new developer release
[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) // inherit info that ICS does not give from previous board
4372               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4373                 ChessSquare old, new = boards[moveNum][k][j];
4374                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4375                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4376                   if(old == new) continue;
4377                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4378                   else if(new == WhiteWazir || new == BlackWazir) {
4379                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4380                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4381                       else boards[moveNum][k][j] = old; // preserve type of Gold
4382                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4383                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4384               }
4385           } else {
4386             /* Move from ICS was illegal!?  Punt. */
4387             if (appData.debugMode) {
4388               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4389               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4390             }
4391             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4392             strcat(parseList[moveNum - 1], " ");
4393             strcat(parseList[moveNum - 1], elapsed_time);
4394             moveList[moveNum - 1][0] = NULLCHAR;
4395             fromX = fromY = toX = toY = -1;
4396           }
4397         }
4398   if (appData.debugMode) {
4399     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4400     setbuf(debugFP, NULL);
4401   }
4402
4403 #if ZIPPY
4404         /* Send move to chess program (BEFORE animating it). */
4405         if (appData.zippyPlay && !newGame && newMove &&
4406            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4407
4408             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4409                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4410                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4411                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4412                             move_str);
4413                     DisplayError(str, 0);
4414                 } else {
4415                     if (first.sendTime) {
4416                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4417                     }
4418                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4419                     if (firstMove && !bookHit) {
4420                         firstMove = FALSE;
4421                         if (first.useColors) {
4422                           SendToProgram(gameMode == IcsPlayingWhite ?
4423                                         "white\ngo\n" :
4424                                         "black\ngo\n", &first);
4425                         } else {
4426                           SendToProgram("go\n", &first);
4427                         }
4428                         first.maybeThinking = TRUE;
4429                     }
4430                 }
4431             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4432               if (moveList[moveNum - 1][0] == NULLCHAR) {
4433                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4434                 DisplayError(str, 0);
4435               } else {
4436                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4437                 SendMoveToProgram(moveNum - 1, &first);
4438               }
4439             }
4440         }
4441 #endif
4442     }
4443
4444     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4445         /* If move comes from a remote source, animate it.  If it
4446            isn't remote, it will have already been animated. */
4447         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4448             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4449         }
4450         if (!pausing && appData.highlightLastMove) {
4451             SetHighlights(fromX, fromY, toX, toY);
4452         }
4453     }
4454
4455     /* Start the clocks */
4456     whiteFlag = blackFlag = FALSE;
4457     appData.clockMode = !(basetime == 0 && increment == 0);
4458     if (ticking == 0) {
4459       ics_clock_paused = TRUE;
4460       StopClocks();
4461     } else if (ticking == 1) {
4462       ics_clock_paused = FALSE;
4463     }
4464     if (gameMode == IcsIdle ||
4465         relation == RELATION_OBSERVING_STATIC ||
4466         relation == RELATION_EXAMINING ||
4467         ics_clock_paused)
4468       DisplayBothClocks();
4469     else
4470       StartClocks();
4471
4472     /* Display opponents and material strengths */
4473     if (gameInfo.variant != VariantBughouse &&
4474         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4475         if (tinyLayout || smallLayout) {
4476             if(gameInfo.variant == VariantNormal)
4477               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4478                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4479                     basetime, increment);
4480             else
4481               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4482                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4483                     basetime, increment, (int) gameInfo.variant);
4484         } else {
4485             if(gameInfo.variant == VariantNormal)
4486               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4487                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4488                     basetime, increment);
4489             else
4490               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment, VariantName(gameInfo.variant));
4493         }
4494         DisplayTitle(str);
4495   if (appData.debugMode) {
4496     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4497   }
4498     }
4499
4500
4501     /* Display the board */
4502     if (!pausing && !appData.noGUI) {
4503
4504       if (appData.premove)
4505           if (!gotPremove ||
4506              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4507              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4508               ClearPremoveHighlights();
4509
4510       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4511         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4512       DrawPosition(j, boards[currentMove]);
4513
4514       DisplayMove(moveNum - 1);
4515       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4516             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4517               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4518         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4519       }
4520     }
4521
4522     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4523 #if ZIPPY
4524     if(bookHit) { // [HGM] book: simulate book reply
4525         static char bookMove[MSG_SIZ]; // a bit generous?
4526
4527         programStats.nodes = programStats.depth = programStats.time =
4528         programStats.score = programStats.got_only_move = 0;
4529         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4530
4531         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4532         strcat(bookMove, bookHit);
4533         HandleMachineMove(bookMove, &first);
4534     }
4535 #endif
4536 }
4537
4538 void
4539 GetMoveListEvent()
4540 {
4541     char buf[MSG_SIZ];
4542     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4543         ics_getting_history = H_REQUESTED;
4544         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4545         SendToICS(buf);
4546     }
4547 }
4548
4549 void
4550 AnalysisPeriodicEvent(force)
4551      int force;
4552 {
4553     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4554          && !force) || !appData.periodicUpdates)
4555       return;
4556
4557     /* Send . command to Crafty to collect stats */
4558     SendToProgram(".\n", &first);
4559
4560     /* Don't send another until we get a response (this makes
4561        us stop sending to old Crafty's which don't understand
4562        the "." command (sending illegal cmds resets node count & time,
4563        which looks bad)) */
4564     programStats.ok_to_send = 0;
4565 }
4566
4567 void ics_update_width(new_width)
4568         int new_width;
4569 {
4570         ics_printf("set width %d\n", new_width);
4571 }
4572
4573 void
4574 SendMoveToProgram(moveNum, cps)
4575      int moveNum;
4576      ChessProgramState *cps;
4577 {
4578     char buf[MSG_SIZ];
4579
4580     if (cps->useUsermove) {
4581       SendToProgram("usermove ", cps);
4582     }
4583     if (cps->useSAN) {
4584       char *space;
4585       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4586         int len = space - parseList[moveNum];
4587         memcpy(buf, parseList[moveNum], len);
4588         buf[len++] = '\n';
4589         buf[len] = NULLCHAR;
4590       } else {
4591         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4592       }
4593       SendToProgram(buf, cps);
4594     } else {
4595       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4596         AlphaRank(moveList[moveNum], 4);
4597         SendToProgram(moveList[moveNum], cps);
4598         AlphaRank(moveList[moveNum], 4); // and back
4599       } else
4600       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4601        * the engine. It would be nice to have a better way to identify castle
4602        * moves here. */
4603       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4604                                                                          && cps->useOOCastle) {
4605         int fromX = moveList[moveNum][0] - AAA;
4606         int fromY = moveList[moveNum][1] - ONE;
4607         int toX = moveList[moveNum][2] - AAA;
4608         int toY = moveList[moveNum][3] - ONE;
4609         if((boards[moveNum][fromY][fromX] == WhiteKing
4610             && boards[moveNum][toY][toX] == WhiteRook)
4611            || (boards[moveNum][fromY][fromX] == BlackKing
4612                && boards[moveNum][toY][toX] == BlackRook)) {
4613           if(toX > fromX) SendToProgram("O-O\n", cps);
4614           else SendToProgram("O-O-O\n", cps);
4615         }
4616         else SendToProgram(moveList[moveNum], cps);
4617       }
4618       else SendToProgram(moveList[moveNum], cps);
4619       /* End of additions by Tord */
4620     }
4621
4622     /* [HGM] setting up the opening has brought engine in force mode! */
4623     /*       Send 'go' if we are in a mode where machine should play. */
4624     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4625         (gameMode == TwoMachinesPlay   ||
4626 #if ZIPPY
4627          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4628 #endif
4629          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4630         SendToProgram("go\n", cps);
4631   if (appData.debugMode) {
4632     fprintf(debugFP, "(extra)\n");
4633   }
4634     }
4635     setboardSpoiledMachineBlack = 0;
4636 }
4637
4638 void
4639 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4640      ChessMove moveType;
4641      int fromX, fromY, toX, toY;
4642      char promoChar;
4643 {
4644     char user_move[MSG_SIZ];
4645
4646     switch (moveType) {
4647       default:
4648         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4649                 (int)moveType, fromX, fromY, toX, toY);
4650         DisplayError(user_move + strlen("say "), 0);
4651         break;
4652       case WhiteKingSideCastle:
4653       case BlackKingSideCastle:
4654       case WhiteQueenSideCastleWild:
4655       case BlackQueenSideCastleWild:
4656       /* PUSH Fabien */
4657       case WhiteHSideCastleFR:
4658       case BlackHSideCastleFR:
4659       /* POP Fabien */
4660         snprintf(user_move, MSG_SIZ, "o-o\n");
4661         break;
4662       case WhiteQueenSideCastle:
4663       case BlackQueenSideCastle:
4664       case WhiteKingSideCastleWild:
4665       case BlackKingSideCastleWild:
4666       /* PUSH Fabien */
4667       case WhiteASideCastleFR:
4668       case BlackASideCastleFR:
4669       /* POP Fabien */
4670         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4671         break;
4672       case WhiteNonPromotion:
4673       case BlackNonPromotion:
4674         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4675         break;
4676       case WhitePromotion:
4677       case BlackPromotion:
4678         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4679           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4680                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4681                 PieceToChar(WhiteFerz));
4682         else if(gameInfo.variant == VariantGreat)
4683           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4684                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4685                 PieceToChar(WhiteMan));
4686         else
4687           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4688                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4689                 promoChar);
4690         break;
4691       case WhiteDrop:
4692       case BlackDrop:
4693       drop:
4694         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4695                  ToUpper(PieceToChar((ChessSquare) fromX)),
4696                  AAA + toX, ONE + toY);
4697         break;
4698       case IllegalMove:  /* could be a variant we don't quite understand */
4699         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4700       case NormalMove:
4701       case WhiteCapturesEnPassant:
4702       case BlackCapturesEnPassant:
4703         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4704                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4705         break;
4706     }
4707     SendToICS(user_move);
4708     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4709         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4710 }
4711
4712 void
4713 UploadGameEvent()
4714 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4715     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4716     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4717     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4718         DisplayError("You cannot do this while you are playing or observing", 0);
4719         return;
4720     }
4721     if(gameMode != IcsExamining) { // is this ever not the case?
4722         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4723
4724         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4725           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4726         } else { // on FICS we must first go to general examine mode
4727           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4728         }
4729         if(gameInfo.variant != VariantNormal) {
4730             // try figure out wild number, as xboard names are not always valid on ICS
4731             for(i=1; i<=36; i++) {
4732               snprintf(buf, MSG_SIZ, "wild/%d", i);
4733                 if(StringToVariant(buf) == gameInfo.variant) break;
4734             }
4735             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4736             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4737             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4738         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4739         SendToICS(ics_prefix);
4740         SendToICS(buf);
4741         if(startedFromSetupPosition || backwardMostMove != 0) {
4742           fen = PositionToFEN(backwardMostMove, NULL);
4743           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4744             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4745             SendToICS(buf);
4746           } else { // FICS: everything has to set by separate bsetup commands
4747             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4748             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4749             SendToICS(buf);
4750             if(!WhiteOnMove(backwardMostMove)) {
4751                 SendToICS("bsetup tomove black\n");
4752             }
4753             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4754             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4755             SendToICS(buf);
4756             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4757             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4758             SendToICS(buf);
4759             i = boards[backwardMostMove][EP_STATUS];
4760             if(i >= 0) { // set e.p.
4761               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4762                 SendToICS(buf);
4763             }
4764             bsetup++;
4765           }
4766         }
4767       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4768             SendToICS("bsetup done\n"); // switch to normal examining.
4769     }
4770     for(i = backwardMostMove; i<last; i++) {
4771         char buf[20];
4772         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4773         SendToICS(buf);
4774     }
4775     SendToICS(ics_prefix);
4776     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4777 }
4778
4779 void
4780 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4781      int rf, ff, rt, ft;
4782      char promoChar;
4783      char move[7];
4784 {
4785     if (rf == DROP_RANK) {
4786       sprintf(move, "%c@%c%c\n",
4787                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4788     } else {
4789         if (promoChar == 'x' || promoChar == NULLCHAR) {
4790           sprintf(move, "%c%c%c%c\n",
4791                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4792         } else {
4793             sprintf(move, "%c%c%c%c%c\n",
4794                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4795         }
4796     }
4797 }
4798
4799 void
4800 ProcessICSInitScript(f)
4801      FILE *f;
4802 {
4803     char buf[MSG_SIZ];
4804
4805     while (fgets(buf, MSG_SIZ, f)) {
4806         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4807     }
4808
4809     fclose(f);
4810 }
4811
4812
4813 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4814 void
4815 AlphaRank(char *move, int n)
4816 {
4817 //    char *p = move, c; int x, y;
4818
4819     if (appData.debugMode) {
4820         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4821     }
4822
4823     if(move[1]=='*' &&
4824        move[2]>='0' && move[2]<='9' &&
4825        move[3]>='a' && move[3]<='x'    ) {
4826         move[1] = '@';
4827         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4828         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4829     } else
4830     if(move[0]>='0' && move[0]<='9' &&
4831        move[1]>='a' && move[1]<='x' &&
4832        move[2]>='0' && move[2]<='9' &&
4833        move[3]>='a' && move[3]<='x'    ) {
4834         /* input move, Shogi -> normal */
4835         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4836         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4837         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4838         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4839     } else
4840     if(move[1]=='@' &&
4841        move[3]>='0' && move[3]<='9' &&
4842        move[2]>='a' && move[2]<='x'    ) {
4843         move[1] = '*';
4844         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4845         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4846     } else
4847     if(
4848        move[0]>='a' && move[0]<='x' &&
4849        move[3]>='0' && move[3]<='9' &&
4850        move[2]>='a' && move[2]<='x'    ) {
4851          /* output move, normal -> Shogi */
4852         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4853         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4854         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4855         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4856         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4857     }
4858     if (appData.debugMode) {
4859         fprintf(debugFP, "   out = '%s'\n", move);
4860     }
4861 }
4862
4863 char yy_textstr[8000];
4864
4865 /* Parser for moves from gnuchess, ICS, or user typein box */
4866 Boolean
4867 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4868      char *move;
4869      int moveNum;
4870      ChessMove *moveType;
4871      int *fromX, *fromY, *toX, *toY;
4872      char *promoChar;
4873 {
4874     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4875
4876     switch (*moveType) {
4877       case WhitePromotion:
4878       case BlackPromotion:
4879       case WhiteNonPromotion:
4880       case BlackNonPromotion:
4881       case NormalMove:
4882       case WhiteCapturesEnPassant:
4883       case BlackCapturesEnPassant:
4884       case WhiteKingSideCastle:
4885       case WhiteQueenSideCastle:
4886       case BlackKingSideCastle:
4887       case BlackQueenSideCastle:
4888       case WhiteKingSideCastleWild:
4889       case WhiteQueenSideCastleWild:
4890       case BlackKingSideCastleWild:
4891       case BlackQueenSideCastleWild:
4892       /* Code added by Tord: */
4893       case WhiteHSideCastleFR:
4894       case WhiteASideCastleFR:
4895       case BlackHSideCastleFR:
4896       case BlackASideCastleFR:
4897       /* End of code added by Tord */
4898       case IllegalMove:         /* bug or odd chess variant */
4899         *fromX = currentMoveString[0] - AAA;
4900         *fromY = currentMoveString[1] - ONE;
4901         *toX = currentMoveString[2] - AAA;
4902         *toY = currentMoveString[3] - ONE;
4903         *promoChar = currentMoveString[4];
4904         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4905             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4906     if (appData.debugMode) {
4907         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4908     }
4909             *fromX = *fromY = *toX = *toY = 0;
4910             return FALSE;
4911         }
4912         if (appData.testLegality) {
4913           return (*moveType != IllegalMove);
4914         } else {
4915           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4916                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4917         }
4918
4919       case WhiteDrop:
4920       case BlackDrop:
4921         *fromX = *moveType == WhiteDrop ?
4922           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4923           (int) CharToPiece(ToLower(currentMoveString[0]));
4924         *fromY = DROP_RANK;
4925         *toX = currentMoveString[2] - AAA;
4926         *toY = currentMoveString[3] - ONE;
4927         *promoChar = NULLCHAR;
4928         return TRUE;
4929
4930       case AmbiguousMove:
4931       case ImpossibleMove:
4932       case EndOfFile:
4933       case ElapsedTime:
4934       case Comment:
4935       case PGNTag:
4936       case NAG:
4937       case WhiteWins:
4938       case BlackWins:
4939       case GameIsDrawn:
4940       default:
4941     if (appData.debugMode) {
4942         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4943     }
4944         /* bug? */
4945         *fromX = *fromY = *toX = *toY = 0;
4946         *promoChar = NULLCHAR;
4947         return FALSE;
4948     }
4949 }
4950
4951
4952 void
4953 ParsePV(char *pv, Boolean storeComments)
4954 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4955   int fromX, fromY, toX, toY; char promoChar;
4956   ChessMove moveType;
4957   Boolean valid;
4958   int nr = 0;
4959
4960   endPV = forwardMostMove;
4961   do {
4962     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4963     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4964     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4965 if(appData.debugMode){
4966 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4967 }
4968     if(!valid && nr == 0 &&
4969        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4970         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4971         // Hande case where played move is different from leading PV move
4972         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4973         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4974         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4975         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4976           endPV += 2; // if position different, keep this
4977           moveList[endPV-1][0] = fromX + AAA;
4978           moveList[endPV-1][1] = fromY + ONE;
4979           moveList[endPV-1][2] = toX + AAA;
4980           moveList[endPV-1][3] = toY + ONE;
4981           parseList[endPV-1][0] = NULLCHAR;
4982           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4983         }
4984       }
4985     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4986     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4987     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4988     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4989         valid++; // allow comments in PV
4990         continue;
4991     }
4992     nr++;
4993     if(endPV+1 > framePtr) break; // no space, truncate
4994     if(!valid) break;
4995     endPV++;
4996     CopyBoard(boards[endPV], boards[endPV-1]);
4997     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4998     moveList[endPV-1][0] = fromX + AAA;
4999     moveList[endPV-1][1] = fromY + ONE;
5000     moveList[endPV-1][2] = toX + AAA;
5001     moveList[endPV-1][3] = toY + ONE;
5002     moveList[endPV-1][4] = promoChar;
5003     moveList[endPV-1][5] = NULLCHAR;
5004     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5005     if(storeComments)
5006         CoordsToAlgebraic(boards[endPV - 1],
5007                              PosFlags(endPV - 1),
5008                              fromY, fromX, toY, toX, promoChar,
5009                              parseList[endPV - 1]);
5010     else
5011         parseList[endPV-1][0] = NULLCHAR;
5012   } while(valid);
5013   currentMove = endPV;
5014   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5015   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5016                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5017   DrawPosition(TRUE, boards[currentMove]);
5018 }
5019
5020 static int lastX, lastY;
5021
5022 Boolean
5023 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5024 {
5025         int startPV;
5026         char *p;
5027
5028         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5029         lastX = x; lastY = y;
5030         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5031         startPV = index;
5032         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5033         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5034         index = startPV;
5035         do{ while(buf[index] && buf[index] != '\n') index++;
5036         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5037         buf[index] = 0;
5038         ParsePV(buf+startPV, FALSE);
5039         *start = startPV; *end = index-1;
5040         return TRUE;
5041 }
5042
5043 Boolean
5044 LoadPV(int x, int y)
5045 { // called on right mouse click to load PV
5046   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5047   lastX = x; lastY = y;
5048   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5049   return TRUE;
5050 }
5051
5052 void
5053 UnLoadPV()
5054 {
5055   if(endPV < 0) return;
5056   endPV = -1;
5057   currentMove = forwardMostMove;
5058   ClearPremoveHighlights();
5059   DrawPosition(TRUE, boards[currentMove]);
5060 }
5061
5062 void
5063 MovePV(int x, int y, int h)
5064 { // step through PV based on mouse coordinates (called on mouse move)
5065   int margin = h>>3, step = 0;
5066
5067   if(endPV < 0) return;
5068   // we must somehow check if right button is still down (might be released off board!)
5069   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5070   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5071   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5072   if(!step) return;
5073   lastX = x; lastY = y;
5074   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5075   currentMove += step;
5076   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5077   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5078                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5079   DrawPosition(FALSE, boards[currentMove]);
5080 }
5081
5082
5083 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5084 // All positions will have equal probability, but the current method will not provide a unique
5085 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5086 #define DARK 1
5087 #define LITE 2
5088 #define ANY 3
5089
5090 int squaresLeft[4];
5091 int piecesLeft[(int)BlackPawn];
5092 int seed, nrOfShuffles;
5093
5094 void GetPositionNumber()
5095 {       // sets global variable seed
5096         int i;
5097
5098         seed = appData.defaultFrcPosition;
5099         if(seed < 0) { // randomize based on time for negative FRC position numbers
5100                 for(i=0; i<50; i++) seed += random();
5101                 seed = random() ^ random() >> 8 ^ random() << 8;
5102                 if(seed<0) seed = -seed;
5103         }
5104 }
5105
5106 int put(Board board, int pieceType, int rank, int n, int shade)
5107 // put the piece on the (n-1)-th empty squares of the given shade
5108 {
5109         int i;
5110
5111         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5112                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5113                         board[rank][i] = (ChessSquare) pieceType;
5114                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5115                         squaresLeft[ANY]--;
5116                         piecesLeft[pieceType]--;
5117                         return i;
5118                 }
5119         }
5120         return -1;
5121 }
5122
5123
5124 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5125 // calculate where the next piece goes, (any empty square), and put it there
5126 {
5127         int i;
5128
5129         i = seed % squaresLeft[shade];
5130         nrOfShuffles *= squaresLeft[shade];
5131         seed /= squaresLeft[shade];
5132         put(board, pieceType, rank, i, shade);
5133 }
5134
5135 void AddTwoPieces(Board board, int pieceType, int rank)
5136 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5137 {
5138         int i, n=squaresLeft[ANY], j=n-1, k;
5139
5140         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5141         i = seed % k;  // pick one
5142         nrOfShuffles *= k;
5143         seed /= k;
5144         while(i >= j) i -= j--;
5145         j = n - 1 - j; i += j;
5146         put(board, pieceType, rank, j, ANY);
5147         put(board, pieceType, rank, i, ANY);
5148 }
5149
5150 void SetUpShuffle(Board board, int number)
5151 {
5152         int i, p, first=1;
5153
5154         GetPositionNumber(); nrOfShuffles = 1;
5155
5156         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5157         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5158         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5159
5160         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5161
5162         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5163             p = (int) board[0][i];
5164             if(p < (int) BlackPawn) piecesLeft[p] ++;
5165             board[0][i] = EmptySquare;
5166         }
5167
5168         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5169             // shuffles restricted to allow normal castling put KRR first
5170             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5171                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5172             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5173                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5174             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5175                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5176             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5177                 put(board, WhiteRook, 0, 0, ANY);
5178             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5179         }
5180
5181         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5182             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5183             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5184                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5185                 while(piecesLeft[p] >= 2) {
5186                     AddOnePiece(board, p, 0, LITE);
5187                     AddOnePiece(board, p, 0, DARK);
5188                 }
5189                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5190             }
5191
5192         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5193             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5194             // but we leave King and Rooks for last, to possibly obey FRC restriction
5195             if(p == (int)WhiteRook) continue;
5196             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5197             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5198         }
5199
5200         // now everything is placed, except perhaps King (Unicorn) and Rooks
5201
5202         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5203             // Last King gets castling rights
5204             while(piecesLeft[(int)WhiteUnicorn]) {
5205                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5206                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5207             }
5208
5209             while(piecesLeft[(int)WhiteKing]) {
5210                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5211                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5212             }
5213
5214
5215         } else {
5216             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5217             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5218         }
5219
5220         // Only Rooks can be left; simply place them all
5221         while(piecesLeft[(int)WhiteRook]) {
5222                 i = put(board, WhiteRook, 0, 0, ANY);
5223                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5224                         if(first) {
5225                                 first=0;
5226                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5227                         }
5228                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5229                 }
5230         }
5231         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5232             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5233         }
5234
5235         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5236 }
5237
5238 int SetCharTable( char *table, const char * map )
5239 /* [HGM] moved here from winboard.c because of its general usefulness */
5240 /*       Basically a safe strcpy that uses the last character as King */
5241 {
5242     int result = FALSE; int NrPieces;
5243
5244     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5245                     && NrPieces >= 12 && !(NrPieces&1)) {
5246         int i; /* [HGM] Accept even length from 12 to 34 */
5247
5248         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5249         for( i=0; i<NrPieces/2-1; i++ ) {
5250             table[i] = map[i];
5251             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5252         }
5253         table[(int) WhiteKing]  = map[NrPieces/2-1];
5254         table[(int) BlackKing]  = map[NrPieces-1];
5255
5256         result = TRUE;
5257     }
5258
5259     return result;
5260 }
5261
5262 void Prelude(Board board)
5263 {       // [HGM] superchess: random selection of exo-pieces
5264         int i, j, k; ChessSquare p;
5265         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5266
5267         GetPositionNumber(); // use FRC position number
5268
5269         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5270             SetCharTable(pieceToChar, appData.pieceToCharTable);
5271             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5272                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5273         }
5274
5275         j = seed%4;                 seed /= 4;
5276         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5277         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5278         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5279         j = seed%3 + (seed%3 >= j); seed /= 3;
5280         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5281         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5282         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5283         j = seed%3;                 seed /= 3;
5284         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5285         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5286         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5287         j = seed%2 + (seed%2 >= j); seed /= 2;
5288         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5289         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5290         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5291         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5292         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5293         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5294         put(board, exoPieces[0],    0, 0, ANY);
5295         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5296 }
5297
5298 void
5299 InitPosition(redraw)
5300      int redraw;
5301 {
5302     ChessSquare (* pieces)[BOARD_FILES];
5303     int i, j, pawnRow, overrule,
5304     oldx = gameInfo.boardWidth,
5305     oldy = gameInfo.boardHeight,
5306     oldh = gameInfo.holdingsWidth;
5307     static int oldv;
5308
5309     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5310
5311     /* [AS] Initialize pv info list [HGM] and game status */
5312     {
5313         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5314             pvInfoList[i].depth = 0;
5315             boards[i][EP_STATUS] = EP_NONE;
5316             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5317         }
5318
5319         initialRulePlies = 0; /* 50-move counter start */
5320
5321         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5322         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5323     }
5324
5325
5326     /* [HGM] logic here is completely changed. In stead of full positions */
5327     /* the initialized data only consist of the two backranks. The switch */
5328     /* selects which one we will use, which is than copied to the Board   */
5329     /* initialPosition, which for the rest is initialized by Pawns and    */
5330     /* empty squares. This initial position is then copied to boards[0],  */
5331     /* possibly after shuffling, so that it remains available.            */
5332
5333     gameInfo.holdingsWidth = 0; /* default board sizes */
5334     gameInfo.boardWidth    = 8;
5335     gameInfo.boardHeight   = 8;
5336     gameInfo.holdingsSize  = 0;
5337     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5338     for(i=0; i<BOARD_FILES-2; i++)
5339       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5340     initialPosition[EP_STATUS] = EP_NONE;
5341     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5342     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5343          SetCharTable(pieceNickName, appData.pieceNickNames);
5344     else SetCharTable(pieceNickName, "............");
5345     pieces = FIDEArray;
5346
5347     switch (gameInfo.variant) {
5348     case VariantFischeRandom:
5349       shuffleOpenings = TRUE;
5350     default:
5351       break;
5352     case VariantShatranj:
5353       pieces = ShatranjArray;
5354       nrCastlingRights = 0;
5355       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5356       break;
5357     case VariantMakruk:
5358       pieces = makrukArray;
5359       nrCastlingRights = 0;
5360       startedFromSetupPosition = TRUE;
5361       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5362       break;
5363     case VariantTwoKings:
5364       pieces = twoKingsArray;
5365       break;
5366     case VariantCapaRandom:
5367       shuffleOpenings = TRUE;
5368     case VariantCapablanca:
5369       pieces = CapablancaArray;
5370       gameInfo.boardWidth = 10;
5371       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5372       break;
5373     case VariantGothic:
5374       pieces = GothicArray;
5375       gameInfo.boardWidth = 10;
5376       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5377       break;
5378     case VariantSChess:
5379       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5380       gameInfo.holdingsSize = 7;
5381       break;
5382     case VariantJanus:
5383       pieces = JanusArray;
5384       gameInfo.boardWidth = 10;
5385       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5386       nrCastlingRights = 6;
5387         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5388         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5389         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5390         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5391         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5392         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5393       break;
5394     case VariantFalcon:
5395       pieces = FalconArray;
5396       gameInfo.boardWidth = 10;
5397       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5398       break;
5399     case VariantXiangqi:
5400       pieces = XiangqiArray;
5401       gameInfo.boardWidth  = 9;
5402       gameInfo.boardHeight = 10;
5403       nrCastlingRights = 0;
5404       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5405       break;
5406     case VariantShogi:
5407       pieces = ShogiArray;
5408       gameInfo.boardWidth  = 9;
5409       gameInfo.boardHeight = 9;
5410       gameInfo.holdingsSize = 7;
5411       nrCastlingRights = 0;
5412       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5413       break;
5414     case VariantCourier:
5415       pieces = CourierArray;
5416       gameInfo.boardWidth  = 12;
5417       nrCastlingRights = 0;
5418       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5419       break;
5420     case VariantKnightmate:
5421       pieces = KnightmateArray;
5422       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5423       break;
5424     case VariantSpartan:
5425       pieces = SpartanArray;
5426       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5427       break;
5428     case VariantFairy:
5429       pieces = fairyArray;
5430       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5431       break;
5432     case VariantGreat:
5433       pieces = GreatArray;
5434       gameInfo.boardWidth = 10;
5435       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5436       gameInfo.holdingsSize = 8;
5437       break;
5438     case VariantSuper:
5439       pieces = FIDEArray;
5440       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5441       gameInfo.holdingsSize = 8;
5442       startedFromSetupPosition = TRUE;
5443       break;
5444     case VariantCrazyhouse:
5445     case VariantBughouse:
5446       pieces = FIDEArray;
5447       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5448       gameInfo.holdingsSize = 5;
5449       break;
5450     case VariantWildCastle:
5451       pieces = FIDEArray;
5452       /* !!?shuffle with kings guaranteed to be on d or e file */
5453       shuffleOpenings = 1;
5454       break;
5455     case VariantNoCastle:
5456       pieces = FIDEArray;
5457       nrCastlingRights = 0;
5458       /* !!?unconstrained back-rank shuffle */
5459       shuffleOpenings = 1;
5460       break;
5461     }
5462
5463     overrule = 0;
5464     if(appData.NrFiles >= 0) {
5465         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5466         gameInfo.boardWidth = appData.NrFiles;
5467     }
5468     if(appData.NrRanks >= 0) {
5469         gameInfo.boardHeight = appData.NrRanks;
5470     }
5471     if(appData.holdingsSize >= 0) {
5472         i = appData.holdingsSize;
5473         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5474         gameInfo.holdingsSize = i;
5475     }
5476     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5477     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5478         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5479
5480     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5481     if(pawnRow < 1) pawnRow = 1;
5482     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5483
5484     /* User pieceToChar list overrules defaults */
5485     if(appData.pieceToCharTable != NULL)
5486         SetCharTable(pieceToChar, appData.pieceToCharTable);
5487
5488     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5489
5490         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5491             s = (ChessSquare) 0; /* account holding counts in guard band */
5492         for( i=0; i<BOARD_HEIGHT; i++ )
5493             initialPosition[i][j] = s;
5494
5495         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5496         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5497         initialPosition[pawnRow][j] = WhitePawn;
5498         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5499         if(gameInfo.variant == VariantXiangqi) {
5500             if(j&1) {
5501                 initialPosition[pawnRow][j] =
5502                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5503                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5504                    initialPosition[2][j] = WhiteCannon;
5505                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5506                 }
5507             }
5508         }
5509         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5510     }
5511     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5512
5513             j=BOARD_LEFT+1;
5514             initialPosition[1][j] = WhiteBishop;
5515             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5516             j=BOARD_RGHT-2;
5517             initialPosition[1][j] = WhiteRook;
5518             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5519     }
5520
5521     if( nrCastlingRights == -1) {
5522         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5523         /*       This sets default castling rights from none to normal corners   */
5524         /* Variants with other castling rights must set them themselves above    */
5525         nrCastlingRights = 6;
5526
5527         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5528         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5529         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5530         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5531         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5532         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5533      }
5534
5535      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5536      if(gameInfo.variant == VariantGreat) { // promotion commoners
5537         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5538         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5539         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5540         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5541      }
5542      if( gameInfo.variant == VariantSChess ) {
5543       initialPosition[1][0] = BlackMarshall;
5544       initialPosition[2][0] = BlackAngel;
5545       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5546       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5547       initialPosition[1][1] = initialPosition[2][1] = 
5548       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5549      }
5550   if (appData.debugMode) {
5551     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5552   }
5553     if(shuffleOpenings) {
5554         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5555         startedFromSetupPosition = TRUE;
5556     }
5557     if(startedFromPositionFile) {
5558       /* [HGM] loadPos: use PositionFile for every new game */
5559       CopyBoard(initialPosition, filePosition);
5560       for(i=0; i<nrCastlingRights; i++)
5561           initialRights[i] = filePosition[CASTLING][i];
5562       startedFromSetupPosition = TRUE;
5563     }
5564
5565     CopyBoard(boards[0], initialPosition);
5566
5567     if(oldx != gameInfo.boardWidth ||
5568        oldy != gameInfo.boardHeight ||
5569        oldv != gameInfo.variant ||
5570        oldh != gameInfo.holdingsWidth
5571                                          )
5572             InitDrawingSizes(-2 ,0);
5573
5574     oldv = gameInfo.variant;
5575     if (redraw)
5576       DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 SendBoard(cps, moveNum)
5581      ChessProgramState *cps;
5582      int moveNum;
5583 {
5584     char message[MSG_SIZ];
5585
5586     if (cps->useSetboard) {
5587       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5588       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5589       SendToProgram(message, cps);
5590       free(fen);
5591
5592     } else {
5593       ChessSquare *bp;
5594       int i, j;
5595       /* Kludge to set black to move, avoiding the troublesome and now
5596        * deprecated "black" command.
5597        */
5598       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5599         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5600
5601       SendToProgram("edit\n", cps);
5602       SendToProgram("#\n", cps);
5603       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5604         bp = &boards[moveNum][i][BOARD_LEFT];
5605         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5606           if ((int) *bp < (int) BlackPawn) {
5607             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5608                     AAA + j, ONE + i);
5609             if(message[0] == '+' || message[0] == '~') {
5610               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5611                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5612                         AAA + j, ONE + i);
5613             }
5614             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5615                 message[1] = BOARD_RGHT   - 1 - j + '1';
5616                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5617             }
5618             SendToProgram(message, cps);
5619           }
5620         }
5621       }
5622
5623       SendToProgram("c\n", cps);
5624       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5625         bp = &boards[moveNum][i][BOARD_LEFT];
5626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5627           if (((int) *bp != (int) EmptySquare)
5628               && ((int) *bp >= (int) BlackPawn)) {
5629             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5630                     AAA + j, ONE + i);
5631             if(message[0] == '+' || message[0] == '~') {
5632               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5633                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5634                         AAA + j, ONE + i);
5635             }
5636             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5637                 message[1] = BOARD_RGHT   - 1 - j + '1';
5638                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5639             }
5640             SendToProgram(message, cps);
5641           }
5642         }
5643       }
5644
5645       SendToProgram(".\n", cps);
5646     }
5647     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5648 }
5649
5650 static int autoQueen; // [HGM] oneclick
5651
5652 int
5653 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5654 {
5655     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5656     /* [HGM] add Shogi promotions */
5657     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5658     ChessSquare piece;
5659     ChessMove moveType;
5660     Boolean premove;
5661
5662     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5663     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5664
5665     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5666       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5667         return FALSE;
5668
5669     piece = boards[currentMove][fromY][fromX];
5670     if(gameInfo.variant == VariantShogi) {
5671         promotionZoneSize = BOARD_HEIGHT/3;
5672         highestPromotingPiece = (int)WhiteFerz;
5673     } else if(gameInfo.variant == VariantMakruk) {
5674         promotionZoneSize = 3;
5675     }
5676
5677     // Treat Lance as Pawn when it is not representing Amazon
5678     if(gameInfo.variant != VariantSuper) {
5679         if(piece == WhiteLance) piece = WhitePawn; else
5680         if(piece == BlackLance) piece = BlackPawn;
5681     }
5682
5683     // next weed out all moves that do not touch the promotion zone at all
5684     if((int)piece >= BlackPawn) {
5685         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5686              return FALSE;
5687         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5688     } else {
5689         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5690            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5691     }
5692
5693     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5694
5695     // weed out mandatory Shogi promotions
5696     if(gameInfo.variant == VariantShogi) {
5697         if(piece >= BlackPawn) {
5698             if(toY == 0 && piece == BlackPawn ||
5699                toY == 0 && piece == BlackQueen ||
5700                toY <= 1 && piece == BlackKnight) {
5701                 *promoChoice = '+';
5702                 return FALSE;
5703             }
5704         } else {
5705             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5706                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5707                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5708                 *promoChoice = '+';
5709                 return FALSE;
5710             }
5711         }
5712     }
5713
5714     // weed out obviously illegal Pawn moves
5715     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5716         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5717         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5718         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5719         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5720         // note we are not allowed to test for valid (non-)capture, due to premove
5721     }
5722
5723     // we either have a choice what to promote to, or (in Shogi) whether to promote
5724     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5725         *promoChoice = PieceToChar(BlackFerz);  // no choice
5726         return FALSE;
5727     }
5728     // no sense asking what we must promote to if it is going to explode...
5729     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5730         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5731         return FALSE;
5732     }
5733     if(autoQueen) { // predetermined
5734         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5735              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5736         else *promoChoice = PieceToChar(BlackQueen);
5737         return FALSE;
5738     }
5739
5740     // suppress promotion popup on illegal moves that are not premoves
5741     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5742               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5743     if(appData.testLegality && !premove) {
5744         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5745                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5746         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5747             return FALSE;
5748     }
5749
5750     return TRUE;
5751 }
5752
5753 int
5754 InPalace(row, column)
5755      int row, column;
5756 {   /* [HGM] for Xiangqi */
5757     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5758          column < (BOARD_WIDTH + 4)/2 &&
5759          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5760     return FALSE;
5761 }
5762
5763 int
5764 PieceForSquare (x, y)
5765      int x;
5766      int y;
5767 {
5768   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5769      return -1;
5770   else
5771      return boards[currentMove][y][x];
5772 }
5773
5774 int
5775 OKToStartUserMove(x, y)
5776      int x, y;
5777 {
5778     ChessSquare from_piece;
5779     int white_piece;
5780
5781     if (matchMode) return FALSE;
5782     if (gameMode == EditPosition) return TRUE;
5783
5784     if (x >= 0 && y >= 0)
5785       from_piece = boards[currentMove][y][x];
5786     else
5787       from_piece = EmptySquare;
5788
5789     if (from_piece == EmptySquare) return FALSE;
5790
5791     white_piece = (int)from_piece >= (int)WhitePawn &&
5792       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5793
5794     switch (gameMode) {
5795       case PlayFromGameFile:
5796       case AnalyzeFile:
5797       case TwoMachinesPlay:
5798       case EndOfGame:
5799         return FALSE;
5800
5801       case IcsObserving:
5802       case IcsIdle:
5803         return FALSE;
5804
5805       case MachinePlaysWhite:
5806       case IcsPlayingBlack:
5807         if (appData.zippyPlay) return FALSE;
5808         if (white_piece) {
5809             DisplayMoveError(_("You are playing Black"));
5810             return FALSE;
5811         }
5812         break;
5813
5814       case MachinePlaysBlack:
5815       case IcsPlayingWhite:
5816         if (appData.zippyPlay) return FALSE;
5817         if (!white_piece) {
5818             DisplayMoveError(_("You are playing White"));
5819             return FALSE;
5820         }
5821         break;
5822
5823       case EditGame:
5824         if (!white_piece && WhiteOnMove(currentMove)) {
5825             DisplayMoveError(_("It is White's turn"));
5826             return FALSE;
5827         }
5828         if (white_piece && !WhiteOnMove(currentMove)) {
5829             DisplayMoveError(_("It is Black's turn"));
5830             return FALSE;
5831         }
5832         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5833             /* Editing correspondence game history */
5834             /* Could disallow this or prompt for confirmation */
5835             cmailOldMove = -1;
5836         }
5837         break;
5838
5839       case BeginningOfGame:
5840         if (appData.icsActive) return FALSE;
5841         if (!appData.noChessProgram) {
5842             if (!white_piece) {
5843                 DisplayMoveError(_("You are playing White"));
5844                 return FALSE;
5845             }
5846         }
5847         break;
5848
5849       case Training:
5850         if (!white_piece && WhiteOnMove(currentMove)) {
5851             DisplayMoveError(_("It is White's turn"));
5852             return FALSE;
5853         }
5854         if (white_piece && !WhiteOnMove(currentMove)) {
5855             DisplayMoveError(_("It is Black's turn"));
5856             return FALSE;
5857         }
5858         break;
5859
5860       default:
5861       case IcsExamining:
5862         break;
5863     }
5864     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5865         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5866         && gameMode != AnalyzeFile && gameMode != Training) {
5867         DisplayMoveError(_("Displayed position is not current"));
5868         return FALSE;
5869     }
5870     return TRUE;
5871 }
5872
5873 Boolean
5874 OnlyMove(int *x, int *y, Boolean captures) {
5875     DisambiguateClosure cl;
5876     if (appData.zippyPlay) return FALSE;
5877     switch(gameMode) {
5878       case MachinePlaysBlack:
5879       case IcsPlayingWhite:
5880       case BeginningOfGame:
5881         if(!WhiteOnMove(currentMove)) return FALSE;
5882         break;
5883       case MachinePlaysWhite:
5884       case IcsPlayingBlack:
5885         if(WhiteOnMove(currentMove)) return FALSE;
5886         break;
5887       case EditGame:
5888         break;
5889       default:
5890         return FALSE;
5891     }
5892     cl.pieceIn = EmptySquare;
5893     cl.rfIn = *y;
5894     cl.ffIn = *x;
5895     cl.rtIn = -1;
5896     cl.ftIn = -1;
5897     cl.promoCharIn = NULLCHAR;
5898     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5899     if( cl.kind == NormalMove ||
5900         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5901         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5902         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5903       fromX = cl.ff;
5904       fromY = cl.rf;
5905       *x = cl.ft;
5906       *y = cl.rt;
5907       return TRUE;
5908     }
5909     if(cl.kind != ImpossibleMove) return FALSE;
5910     cl.pieceIn = EmptySquare;
5911     cl.rfIn = -1;
5912     cl.ffIn = -1;
5913     cl.rtIn = *y;
5914     cl.ftIn = *x;
5915     cl.promoCharIn = NULLCHAR;
5916     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5917     if( cl.kind == NormalMove ||
5918         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5919         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5920         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5921       fromX = cl.ff;
5922       fromY = cl.rf;
5923       *x = cl.ft;
5924       *y = cl.rt;
5925       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5926       return TRUE;
5927     }
5928     return FALSE;
5929 }
5930
5931 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5932 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5933 int lastLoadGameUseList = FALSE;
5934 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5935 ChessMove lastLoadGameStart = EndOfFile;
5936
5937 void
5938 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5939      int fromX, fromY, toX, toY;
5940      int promoChar;
5941 {
5942     ChessMove moveType;
5943     ChessSquare pdown, pup;
5944
5945     /* Check if the user is playing in turn.  This is complicated because we
5946        let the user "pick up" a piece before it is his turn.  So the piece he
5947        tried to pick up may have been captured by the time he puts it down!
5948        Therefore we use the color the user is supposed to be playing in this
5949        test, not the color of the piece that is currently on the starting
5950        square---except in EditGame mode, where the user is playing both
5951        sides; fortunately there the capture race can't happen.  (It can
5952        now happen in IcsExamining mode, but that's just too bad.  The user
5953        will get a somewhat confusing message in that case.)
5954        */
5955
5956     switch (gameMode) {
5957       case PlayFromGameFile:
5958       case AnalyzeFile:
5959       case TwoMachinesPlay:
5960       case EndOfGame:
5961       case IcsObserving:
5962       case IcsIdle:
5963         /* We switched into a game mode where moves are not accepted,
5964            perhaps while the mouse button was down. */
5965         return;
5966
5967       case MachinePlaysWhite:
5968         /* User is moving for Black */
5969         if (WhiteOnMove(currentMove)) {
5970             DisplayMoveError(_("It is White's turn"));
5971             return;
5972         }
5973         break;
5974
5975       case MachinePlaysBlack:
5976         /* User is moving for White */
5977         if (!WhiteOnMove(currentMove)) {
5978             DisplayMoveError(_("It is Black's turn"));
5979             return;
5980         }
5981         break;
5982
5983       case EditGame:
5984       case IcsExamining:
5985       case BeginningOfGame:
5986       case AnalyzeMode:
5987       case Training:
5988         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
5989         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5990             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5991             /* User is moving for Black */
5992             if (WhiteOnMove(currentMove)) {
5993                 DisplayMoveError(_("It is White's turn"));
5994                 return;
5995             }
5996         } else {
5997             /* User is moving for White */
5998             if (!WhiteOnMove(currentMove)) {
5999                 DisplayMoveError(_("It is Black's turn"));
6000                 return;
6001             }
6002         }
6003         break;
6004
6005       case IcsPlayingBlack:
6006         /* User is moving for Black */
6007         if (WhiteOnMove(currentMove)) {
6008             if (!appData.premove) {
6009                 DisplayMoveError(_("It is White's turn"));
6010             } else if (toX >= 0 && toY >= 0) {
6011                 premoveToX = toX;
6012                 premoveToY = toY;
6013                 premoveFromX = fromX;
6014                 premoveFromY = fromY;
6015                 premovePromoChar = promoChar;
6016                 gotPremove = 1;
6017                 if (appData.debugMode)
6018                     fprintf(debugFP, "Got premove: fromX %d,"
6019                             "fromY %d, toX %d, toY %d\n",
6020                             fromX, fromY, toX, toY);
6021             }
6022             return;
6023         }
6024         break;
6025
6026       case IcsPlayingWhite:
6027         /* User is moving for White */
6028         if (!WhiteOnMove(currentMove)) {
6029             if (!appData.premove) {
6030                 DisplayMoveError(_("It is Black's turn"));
6031             } else if (toX >= 0 && toY >= 0) {
6032                 premoveToX = toX;
6033                 premoveToY = toY;
6034                 premoveFromX = fromX;
6035                 premoveFromY = fromY;
6036                 premovePromoChar = promoChar;
6037                 gotPremove = 1;
6038                 if (appData.debugMode)
6039                     fprintf(debugFP, "Got premove: fromX %d,"
6040                             "fromY %d, toX %d, toY %d\n",
6041                             fromX, fromY, toX, toY);
6042             }
6043             return;
6044         }
6045         break;
6046
6047       default:
6048         break;
6049
6050       case EditPosition:
6051         /* EditPosition, empty square, or different color piece;
6052            click-click move is possible */
6053         if (toX == -2 || toY == -2) {
6054             boards[0][fromY][fromX] = EmptySquare;
6055             DrawPosition(FALSE, boards[currentMove]);
6056             return;
6057         } else if (toX >= 0 && toY >= 0) {
6058             boards[0][toY][toX] = boards[0][fromY][fromX];
6059             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6060                 if(boards[0][fromY][0] != EmptySquare) {
6061                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6062                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6063                 }
6064             } else
6065             if(fromX == BOARD_RGHT+1) {
6066                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6067                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6068                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6069                 }
6070             } else
6071             boards[0][fromY][fromX] = EmptySquare;
6072             DrawPosition(FALSE, boards[currentMove]);
6073             return;
6074         }
6075         return;
6076     }
6077
6078     if(toX < 0 || toY < 0) return;
6079     pdown = boards[currentMove][fromY][fromX];
6080     pup = boards[currentMove][toY][toX];
6081
6082     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6083     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6084          if( pup != EmptySquare ) return;
6085          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6086            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6087                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6088            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6089            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6090            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6091            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6092          fromY = DROP_RANK;
6093     }
6094
6095     /* [HGM] always test for legality, to get promotion info */
6096     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6097                                          fromY, fromX, toY, toX, promoChar);
6098     /* [HGM] but possibly ignore an IllegalMove result */
6099     if (appData.testLegality) {
6100         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6101             DisplayMoveError(_("Illegal move"));
6102             return;
6103         }
6104     }
6105
6106     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6107 }
6108
6109 /* Common tail of UserMoveEvent and DropMenuEvent */
6110 int
6111 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6112      ChessMove moveType;
6113      int fromX, fromY, toX, toY;
6114      /*char*/int promoChar;
6115 {
6116     char *bookHit = 0;
6117
6118     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6119         // [HGM] superchess: suppress promotions to non-available piece
6120         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6121         if(WhiteOnMove(currentMove)) {
6122             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6123         } else {
6124             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6125         }
6126     }
6127
6128     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6129        move type in caller when we know the move is a legal promotion */
6130     if(moveType == NormalMove && promoChar)
6131         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6132
6133     /* [HGM] <popupFix> The following if has been moved here from
6134        UserMoveEvent(). Because it seemed to belong here (why not allow
6135        piece drops in training games?), and because it can only be
6136        performed after it is known to what we promote. */
6137     if (gameMode == Training) {
6138       /* compare the move played on the board to the next move in the
6139        * game. If they match, display the move and the opponent's response.
6140        * If they don't match, display an error message.
6141        */
6142       int saveAnimate;
6143       Board testBoard;
6144       CopyBoard(testBoard, boards[currentMove]);
6145       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6146
6147       if (CompareBoards(testBoard, boards[currentMove+1])) {
6148         ForwardInner(currentMove+1);
6149
6150         /* Autoplay the opponent's response.
6151          * if appData.animate was TRUE when Training mode was entered,
6152          * the response will be animated.
6153          */
6154         saveAnimate = appData.animate;
6155         appData.animate = animateTraining;
6156         ForwardInner(currentMove+1);
6157         appData.animate = saveAnimate;
6158
6159         /* check for the end of the game */
6160         if (currentMove >= forwardMostMove) {
6161           gameMode = PlayFromGameFile;
6162           ModeHighlight();
6163           SetTrainingModeOff();
6164           DisplayInformation(_("End of game"));
6165         }
6166       } else {
6167         DisplayError(_("Incorrect move"), 0);
6168       }
6169       return 1;
6170     }
6171
6172   /* Ok, now we know that the move is good, so we can kill
6173      the previous line in Analysis Mode */
6174   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6175                                 && currentMove < forwardMostMove) {
6176     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6177     else forwardMostMove = currentMove;
6178   }
6179
6180   /* If we need the chess program but it's dead, restart it */
6181   ResurrectChessProgram();
6182
6183   /* A user move restarts a paused game*/
6184   if (pausing)
6185     PauseEvent();
6186
6187   thinkOutput[0] = NULLCHAR;
6188
6189   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6190
6191   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6192     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6193     return 1;
6194   }
6195
6196   if (gameMode == BeginningOfGame) {
6197     if (appData.noChessProgram) {
6198       gameMode = EditGame;
6199       SetGameInfo();
6200     } else {
6201       char buf[MSG_SIZ];
6202       gameMode = MachinePlaysBlack;
6203       StartClocks();
6204       SetGameInfo();
6205       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6206       DisplayTitle(buf);
6207       if (first.sendName) {
6208         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6209         SendToProgram(buf, &first);
6210       }
6211       StartClocks();
6212     }
6213     ModeHighlight();
6214   }
6215
6216   /* Relay move to ICS or chess engine */
6217   if (appData.icsActive) {
6218     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6219         gameMode == IcsExamining) {
6220       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6221         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6222         SendToICS("draw ");
6223         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6224       }
6225       // also send plain move, in case ICS does not understand atomic claims
6226       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6227       ics_user_moved = 1;
6228     }
6229   } else {
6230     if (first.sendTime && (gameMode == BeginningOfGame ||
6231                            gameMode == MachinePlaysWhite ||
6232                            gameMode == MachinePlaysBlack)) {
6233       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6234     }
6235     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6236          // [HGM] book: if program might be playing, let it use book
6237         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6238         first.maybeThinking = TRUE;
6239     } else SendMoveToProgram(forwardMostMove-1, &first);
6240     if (currentMove == cmailOldMove + 1) {
6241       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6242     }
6243   }
6244
6245   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6246
6247   switch (gameMode) {
6248   case EditGame:
6249     if(appData.testLegality)
6250     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6251     case MT_NONE:
6252     case MT_CHECK:
6253       break;
6254     case MT_CHECKMATE:
6255     case MT_STAINMATE:
6256       if (WhiteOnMove(currentMove)) {
6257         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6258       } else {
6259         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6260       }
6261       break;
6262     case MT_STALEMATE:
6263       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6264       break;
6265     }
6266     break;
6267
6268   case MachinePlaysBlack:
6269   case MachinePlaysWhite:
6270     /* disable certain menu options while machine is thinking */
6271     SetMachineThinkingEnables();
6272     break;
6273
6274   default:
6275     break;
6276   }
6277
6278   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6279
6280   if(bookHit) { // [HGM] book: simulate book reply
6281         static char bookMove[MSG_SIZ]; // a bit generous?
6282
6283         programStats.nodes = programStats.depth = programStats.time =
6284         programStats.score = programStats.got_only_move = 0;
6285         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6286
6287         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6288         strcat(bookMove, bookHit);
6289         HandleMachineMove(bookMove, &first);
6290   }
6291   return 1;
6292 }
6293
6294 void
6295 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6296      Board board;
6297      int flags;
6298      ChessMove kind;
6299      int rf, ff, rt, ft;
6300      VOIDSTAR closure;
6301 {
6302     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6303     Markers *m = (Markers *) closure;
6304     if(rf == fromY && ff == fromX)
6305         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6306                          || kind == WhiteCapturesEnPassant
6307                          || kind == BlackCapturesEnPassant);
6308     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6309 }
6310
6311 void
6312 MarkTargetSquares(int clear)
6313 {
6314   int x, y;
6315   if(!appData.markers || !appData.highlightDragging ||
6316      !appData.testLegality || gameMode == EditPosition) return;
6317   if(clear) {
6318     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6319   } else {
6320     int capt = 0;
6321     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6322     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6323       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6324       if(capt)
6325       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6326     }
6327   }
6328   DrawPosition(TRUE, NULL);
6329 }
6330
6331 int
6332 Explode(Board board, int fromX, int fromY, int toX, int toY)
6333 {
6334     if(gameInfo.variant == VariantAtomic &&
6335        (board[toY][toX] != EmptySquare ||                     // capture?
6336         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6337                          board[fromY][fromX] == BlackPawn   )
6338       )) {
6339         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6340         return TRUE;
6341     }
6342     return FALSE;
6343 }
6344
6345 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6346
6347 void LeftClick(ClickType clickType, int xPix, int yPix)
6348 {
6349     int x, y;
6350     Boolean saveAnimate;
6351     static int second = 0, promotionChoice = 0, dragging = 0;
6352     char promoChoice = NULLCHAR;
6353
6354     if(appData.seekGraph && appData.icsActive && loggedOn &&
6355         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6356         SeekGraphClick(clickType, xPix, yPix, 0);
6357         return;
6358     }
6359
6360     if (clickType == Press) ErrorPopDown();
6361     MarkTargetSquares(1);
6362
6363     x = EventToSquare(xPix, BOARD_WIDTH);
6364     y = EventToSquare(yPix, BOARD_HEIGHT);
6365     if (!flipView && y >= 0) {
6366         y = BOARD_HEIGHT - 1 - y;
6367     }
6368     if (flipView && x >= 0) {
6369         x = BOARD_WIDTH - 1 - x;
6370     }
6371
6372     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6373         if(clickType == Release) return; // ignore upclick of click-click destination
6374         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6375         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6376         if(gameInfo.holdingsWidth &&
6377                 (WhiteOnMove(currentMove)
6378                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6379                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6380             // click in right holdings, for determining promotion piece
6381             ChessSquare p = boards[currentMove][y][x];
6382             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6383             if(p != EmptySquare) {
6384                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6385                 fromX = fromY = -1;
6386                 return;
6387             }
6388         }
6389         DrawPosition(FALSE, boards[currentMove]);
6390         return;
6391     }
6392
6393     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6394     if(clickType == Press
6395             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6396               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6397               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6398         return;
6399
6400     autoQueen = appData.alwaysPromoteToQueen;
6401
6402     if (fromX == -1) {
6403       gatingPiece = EmptySquare;
6404       if (clickType != Press) {
6405         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6406             DragPieceEnd(xPix, yPix); dragging = 0;
6407             DrawPosition(FALSE, NULL);
6408         }
6409         return;
6410       }
6411       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6412             /* First square */
6413             if (OKToStartUserMove(x, y)) {
6414                 fromX = x;
6415                 fromY = y;
6416                 second = 0;
6417                 MarkTargetSquares(0);
6418                 DragPieceBegin(xPix, yPix); dragging = 1;
6419                 if (appData.highlightDragging) {
6420                     SetHighlights(x, y, -1, -1);
6421                 }
6422             }
6423             return;
6424         }
6425     }
6426
6427     /* fromX != -1 */
6428     if (clickType == Press && gameMode != EditPosition) {
6429         ChessSquare fromP;
6430         ChessSquare toP;
6431         int frc;
6432
6433         // ignore off-board to clicks
6434         if(y < 0 || x < 0) return;
6435
6436         /* Check if clicking again on the same color piece */
6437         fromP = boards[currentMove][fromY][fromX];
6438         toP = boards[currentMove][y][x];
6439         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6440         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6441              WhitePawn <= toP && toP <= WhiteKing &&
6442              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6443              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6444             (BlackPawn <= fromP && fromP <= BlackKing &&
6445              BlackPawn <= toP && toP <= BlackKing &&
6446              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6447              !(fromP == BlackKing && toP == BlackRook && frc))) {
6448             /* Clicked again on same color piece -- changed his mind */
6449             second = (x == fromX && y == fromY);
6450            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6451             if (appData.highlightDragging) {
6452                 SetHighlights(x, y, -1, -1);
6453             } else {
6454                 ClearHighlights();
6455             }
6456             if (OKToStartUserMove(x, y)) {
6457                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6458                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6459                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6460                  gatingPiece = boards[currentMove][fromY][fromX];
6461                 else gatingPiece = EmptySquare;
6462                 fromX = x;
6463                 fromY = y; dragging = 1;
6464                 MarkTargetSquares(0);
6465                 DragPieceBegin(xPix, yPix);
6466             }
6467            }
6468            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6469            second = FALSE; 
6470         }
6471         // ignore clicks on holdings
6472         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6473     }
6474
6475     if (clickType == Release && x == fromX && y == fromY) {
6476         DragPieceEnd(xPix, yPix); dragging = 0;
6477         if (appData.animateDragging) {
6478             /* Undo animation damage if any */
6479             DrawPosition(FALSE, NULL);
6480         }
6481         if (second) {
6482             /* Second up/down in same square; just abort move */
6483             second = 0;
6484             fromX = fromY = -1;
6485             gatingPiece = EmptySquare;
6486             ClearHighlights();
6487             gotPremove = 0;
6488             ClearPremoveHighlights();
6489         } else {
6490             /* First upclick in same square; start click-click mode */
6491             SetHighlights(x, y, -1, -1);
6492         }
6493         return;
6494     }
6495
6496     /* we now have a different from- and (possibly off-board) to-square */
6497     /* Completed move */
6498     toX = x;
6499     toY = y;
6500     saveAnimate = appData.animate;
6501     if (clickType == Press) {
6502         /* Finish clickclick move */
6503         if (appData.animate || appData.highlightLastMove) {
6504             SetHighlights(fromX, fromY, toX, toY);
6505         } else {
6506             ClearHighlights();
6507         }
6508     } else {
6509         /* Finish drag move */
6510         if (appData.highlightLastMove) {
6511             SetHighlights(fromX, fromY, toX, toY);
6512         } else {
6513             ClearHighlights();
6514         }
6515         DragPieceEnd(xPix, yPix); dragging = 0;
6516         /* Don't animate move and drag both */
6517         appData.animate = FALSE;
6518     }
6519
6520     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6521     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6522         ChessSquare piece = boards[currentMove][fromY][fromX];
6523         if(gameMode == EditPosition && piece != EmptySquare &&
6524            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6525             int n;
6526
6527             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6528                 n = PieceToNumber(piece - (int)BlackPawn);
6529                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6530                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6531                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6532             } else
6533             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6534                 n = PieceToNumber(piece);
6535                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6536                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6537                 boards[currentMove][n][BOARD_WIDTH-2]++;
6538             }
6539             boards[currentMove][fromY][fromX] = EmptySquare;
6540         }
6541         ClearHighlights();
6542         fromX = fromY = -1;
6543         DrawPosition(TRUE, boards[currentMove]);
6544         return;
6545     }
6546
6547     // off-board moves should not be highlighted
6548     if(x < 0 || y < 0) ClearHighlights();
6549
6550     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6551
6552     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6553         SetHighlights(fromX, fromY, toX, toY);
6554         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6555             // [HGM] super: promotion to captured piece selected from holdings
6556             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6557             promotionChoice = TRUE;
6558             // kludge follows to temporarily execute move on display, without promoting yet
6559             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6560             boards[currentMove][toY][toX] = p;
6561             DrawPosition(FALSE, boards[currentMove]);
6562             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6563             boards[currentMove][toY][toX] = q;
6564             DisplayMessage("Click in holdings to choose piece", "");
6565             return;
6566         }
6567         PromotionPopUp();
6568     } else {
6569         int oldMove = currentMove;
6570         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6571         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6572         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6573         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6574            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6575             DrawPosition(TRUE, boards[currentMove]);
6576         fromX = fromY = -1;
6577     }
6578     appData.animate = saveAnimate;
6579     if (appData.animate || appData.animateDragging) {
6580         /* Undo animation damage if needed */
6581         DrawPosition(FALSE, NULL);
6582     }
6583 }
6584
6585 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6586 {   // front-end-free part taken out of PieceMenuPopup
6587     int whichMenu; int xSqr, ySqr;
6588
6589     if(seekGraphUp) { // [HGM] seekgraph
6590         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6591         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6592         return -2;
6593     }
6594
6595     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6596          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6597         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6598         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6599         if(action == Press)   {
6600             originalFlip = flipView;
6601             flipView = !flipView; // temporarily flip board to see game from partners perspective
6602             DrawPosition(TRUE, partnerBoard);
6603             DisplayMessage(partnerStatus, "");
6604             partnerUp = TRUE;
6605         } else if(action == Release) {
6606             flipView = originalFlip;
6607             DrawPosition(TRUE, boards[currentMove]);
6608             partnerUp = FALSE;
6609         }
6610         return -2;
6611     }
6612
6613     xSqr = EventToSquare(x, BOARD_WIDTH);
6614     ySqr = EventToSquare(y, BOARD_HEIGHT);
6615     if (action == Release) UnLoadPV(); // [HGM] pv
6616     if (action != Press) return -2; // return code to be ignored
6617     switch (gameMode) {
6618       case IcsExamining:
6619         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6620       case EditPosition:
6621         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6622         if (xSqr < 0 || ySqr < 0) return -1;\r
6623         whichMenu = 0; // edit-position menu
6624         break;
6625       case IcsObserving:
6626         if(!appData.icsEngineAnalyze) return -1;
6627       case IcsPlayingWhite:
6628       case IcsPlayingBlack:
6629         if(!appData.zippyPlay) goto noZip;
6630       case AnalyzeMode:
6631       case AnalyzeFile:
6632       case MachinePlaysWhite:
6633       case MachinePlaysBlack:
6634       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6635         if (!appData.dropMenu) {
6636           LoadPV(x, y);
6637           return 2; // flag front-end to grab mouse events
6638         }
6639         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6640            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6641       case EditGame:
6642       noZip:
6643         if (xSqr < 0 || ySqr < 0) return -1;
6644         if (!appData.dropMenu || appData.testLegality &&
6645             gameInfo.variant != VariantBughouse &&
6646             gameInfo.variant != VariantCrazyhouse) return -1;
6647         whichMenu = 1; // drop menu
6648         break;
6649       default:
6650         return -1;
6651     }
6652
6653     if (((*fromX = xSqr) < 0) ||
6654         ((*fromY = ySqr) < 0)) {
6655         *fromX = *fromY = -1;
6656         return -1;
6657     }
6658     if (flipView)
6659       *fromX = BOARD_WIDTH - 1 - *fromX;
6660     else
6661       *fromY = BOARD_HEIGHT - 1 - *fromY;
6662
6663     return whichMenu;
6664 }
6665
6666 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6667 {
6668 //    char * hint = lastHint;
6669     FrontEndProgramStats stats;
6670
6671     stats.which = cps == &first ? 0 : 1;
6672     stats.depth = cpstats->depth;
6673     stats.nodes = cpstats->nodes;
6674     stats.score = cpstats->score;
6675     stats.time = cpstats->time;
6676     stats.pv = cpstats->movelist;
6677     stats.hint = lastHint;
6678     stats.an_move_index = 0;
6679     stats.an_move_count = 0;
6680
6681     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6682         stats.hint = cpstats->move_name;
6683         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6684         stats.an_move_count = cpstats->nr_moves;
6685     }
6686
6687     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
6688
6689     SetProgramStats( &stats );
6690 }
6691
6692 void
6693 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6694 {       // count all piece types
6695         int p, f, r;
6696         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6697         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6698         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6699                 p = board[r][f];
6700                 pCnt[p]++;
6701                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6702                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6703                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6704                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6705                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6706                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6707         }
6708 }
6709
6710 int
6711 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6712 {
6713         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6714         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6715
6716         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6717         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6718         if(myPawns == 2 && nMine == 3) // KPP
6719             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6720         if(myPawns == 1 && nMine == 2) // KP
6721             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6722         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6723             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6724         if(myPawns) return FALSE;
6725         if(pCnt[WhiteRook+side])
6726             return pCnt[BlackRook-side] ||
6727                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6728                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6729                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6730         if(pCnt[WhiteCannon+side]) {
6731             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6732             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6733         }
6734         if(pCnt[WhiteKnight+side])
6735             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6736         return FALSE;
6737 }
6738
6739 int
6740 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6741 {
6742         VariantClass v = gameInfo.variant;
6743
6744         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6745         if(v == VariantShatranj) return TRUE; // always winnable through baring
6746         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6747         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6748
6749         if(v == VariantXiangqi) {
6750                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6751
6752                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6753                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6754                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6755                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6756                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6757                 if(stale) // we have at least one last-rank P plus perhaps C
6758                     return majors // KPKX
6759                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6760                 else // KCA*E*
6761                     return pCnt[WhiteFerz+side] // KCAK
6762                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6763                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6764                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6765
6766         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6767                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6768
6769                 if(nMine == 1) return FALSE; // bare King
6770                 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
6771                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6772                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6773                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6774                 if(pCnt[WhiteKnight+side])
6775                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6776                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6777                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6778                 if(nBishops)
6779                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6780                 if(pCnt[WhiteAlfil+side])
6781                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6782                 if(pCnt[WhiteWazir+side])
6783                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6784         }
6785
6786         return TRUE;
6787 }
6788
6789 int
6790 Adjudicate(ChessProgramState *cps)
6791 {       // [HGM] some adjudications useful with buggy engines
6792         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6793         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6794         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6795         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6796         int k, count = 0; static int bare = 1;
6797         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6798         Boolean canAdjudicate = !appData.icsActive;
6799
6800         // most tests only when we understand the game, i.e. legality-checking on
6801             if( appData.testLegality )
6802             {   /* [HGM] Some more adjudications for obstinate engines */
6803                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6804                 static int moveCount = 6;
6805                 ChessMove result;
6806                 char *reason = NULL;
6807
6808                 /* Count what is on board. */
6809                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6810
6811                 /* Some material-based adjudications that have to be made before stalemate test */
6812                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6813                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6814                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6815                      if(canAdjudicate && appData.checkMates) {
6816                          if(engineOpponent)
6817                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6818                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6819                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6820                          return 1;
6821                      }
6822                 }
6823
6824                 /* Bare King in Shatranj (loses) or Losers (wins) */
6825                 if( nrW == 1 || nrB == 1) {
6826                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6827                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6828                      if(canAdjudicate && appData.checkMates) {
6829                          if(engineOpponent)
6830                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6831                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6832                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6833                          return 1;
6834                      }
6835                   } else
6836                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6837                   {    /* bare King */
6838                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6839                         if(canAdjudicate && appData.checkMates) {
6840                             /* but only adjudicate if adjudication enabled */
6841                             if(engineOpponent)
6842                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6843                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6844                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6845                             return 1;
6846                         }
6847                   }
6848                 } else bare = 1;
6849
6850
6851             // don't wait for engine to announce game end if we can judge ourselves
6852             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6853               case MT_CHECK:
6854                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6855                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6856                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6857                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6858                             checkCnt++;
6859                         if(checkCnt >= 2) {
6860                             reason = "Xboard adjudication: 3rd check";
6861                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6862                             break;
6863                         }
6864                     }
6865                 }
6866               case MT_NONE:
6867               default:
6868                 break;
6869               case MT_STALEMATE:
6870               case MT_STAINMATE:
6871                 reason = "Xboard adjudication: Stalemate";
6872                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6873                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6874                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6875                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6876                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6877                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6878                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6879                                                                         EP_CHECKMATE : EP_WINS);
6880                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6881                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6882                 }
6883                 break;
6884               case MT_CHECKMATE:
6885                 reason = "Xboard adjudication: Checkmate";
6886                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6887                 break;
6888             }
6889
6890                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6891                     case EP_STALEMATE:
6892                         result = GameIsDrawn; break;
6893                     case EP_CHECKMATE:
6894                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6895                     case EP_WINS:
6896                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6897                     default:
6898                         result = EndOfFile;
6899                 }
6900                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6901                     if(engineOpponent)
6902                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6903                     GameEnds( result, reason, GE_XBOARD );
6904                     return 1;
6905                 }
6906
6907                 /* Next absolutely insufficient mating material. */
6908                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6909                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6910                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6911
6912                      /* always flag draws, for judging claims */
6913                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6914
6915                      if(canAdjudicate && appData.materialDraws) {
6916                          /* but only adjudicate them if adjudication enabled */
6917                          if(engineOpponent) {
6918                            SendToProgram("force\n", engineOpponent); // suppress reply
6919                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6920                          }
6921                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6922                          return 1;
6923                      }
6924                 }
6925
6926                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6927                 if(gameInfo.variant == VariantXiangqi ?
6928                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6929                  : nrW + nrB == 4 &&
6930                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6931                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6932                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6933                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6934                    ) ) {
6935                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6936                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6937                           if(engineOpponent) {
6938                             SendToProgram("force\n", engineOpponent); // suppress reply
6939                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6940                           }
6941                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6942                           return 1;
6943                      }
6944                 } else moveCount = 6;
6945             }
6946         if (appData.debugMode) { int i;
6947             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6948                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6949                     appData.drawRepeats);
6950             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6951               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6952
6953         }
6954
6955         // Repetition draws and 50-move rule can be applied independently of legality testing
6956
6957                 /* Check for rep-draws */
6958                 count = 0;
6959                 for(k = forwardMostMove-2;
6960                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6961                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6962                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6963                     k-=2)
6964                 {   int rights=0;
6965                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6966                         /* compare castling rights */
6967                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6968                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6969                                 rights++; /* King lost rights, while rook still had them */
6970                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6971                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6972                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6973                                    rights++; /* but at least one rook lost them */
6974                         }
6975                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6976                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6977                                 rights++;
6978                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6979                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6980                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6981                                    rights++;
6982                         }
6983                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6984                             && appData.drawRepeats > 1) {
6985                              /* adjudicate after user-specified nr of repeats */
6986                              int result = GameIsDrawn;
6987                              char *details = "XBoard adjudication: repetition draw";
6988                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6989                                 // [HGM] xiangqi: check for forbidden perpetuals
6990                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6991                                 for(m=forwardMostMove; m>k; m-=2) {
6992                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6993                                         ourPerpetual = 0; // the current mover did not always check
6994                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6995                                         hisPerpetual = 0; // the opponent did not always check
6996                                 }
6997                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6998                                                                         ourPerpetual, hisPerpetual);
6999                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7000                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7001                                     details = "Xboard adjudication: perpetual checking";
7002                                 } else
7003                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7004                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7005                                 } else
7006                                 // Now check for perpetual chases
7007                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7008                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7009                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7010                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7011                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7012                                         details = "Xboard adjudication: perpetual chasing";
7013                                     } else
7014                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7015                                         break; // Abort repetition-checking loop.
7016                                 }
7017                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7018                              }
7019                              if(engineOpponent) {
7020                                SendToProgram("force\n", engineOpponent); // suppress reply
7021                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7022                              }
7023                              GameEnds( result, details, GE_XBOARD );
7024                              return 1;
7025                         }
7026                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7027                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7028                     }
7029                 }
7030
7031                 /* Now we test for 50-move draws. Determine ply count */
7032                 count = forwardMostMove;
7033                 /* look for last irreversble move */
7034                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7035                     count--;
7036                 /* if we hit starting position, add initial plies */
7037                 if( count == backwardMostMove )
7038                     count -= initialRulePlies;
7039                 count = forwardMostMove - count;
7040                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7041                         // adjust reversible move counter for checks in Xiangqi
7042                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7043                         if(i < backwardMostMove) i = backwardMostMove;
7044                         while(i <= forwardMostMove) {
7045                                 lastCheck = inCheck; // check evasion does not count
7046                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7047                                 if(inCheck || lastCheck) count--; // check does not count
7048                                 i++;
7049                         }
7050                 }
7051                 if( count >= 100)
7052                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7053                          /* this is used to judge if draw claims are legal */
7054                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7055                          if(engineOpponent) {
7056                            SendToProgram("force\n", engineOpponent); // suppress reply
7057                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7058                          }
7059                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7060                          return 1;
7061                 }
7062
7063                 /* if draw offer is pending, treat it as a draw claim
7064                  * when draw condition present, to allow engines a way to
7065                  * claim draws before making their move to avoid a race
7066                  * condition occurring after their move
7067                  */
7068                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7069                          char *p = NULL;
7070                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7071                              p = "Draw claim: 50-move rule";
7072                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7073                              p = "Draw claim: 3-fold repetition";
7074                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7075                              p = "Draw claim: insufficient mating material";
7076                          if( p != NULL && canAdjudicate) {
7077                              if(engineOpponent) {
7078                                SendToProgram("force\n", engineOpponent); // suppress reply
7079                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7080                              }
7081                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7082                              return 1;
7083                          }
7084                 }
7085
7086                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7087                     if(engineOpponent) {
7088                       SendToProgram("force\n", engineOpponent); // suppress reply
7089                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7090                     }
7091                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7092                     return 1;
7093                 }
7094         return 0;
7095 }
7096
7097 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7098 {   // [HGM] book: this routine intercepts moves to simulate book replies
7099     char *bookHit = NULL;
7100
7101     //first determine if the incoming move brings opponent into his book
7102     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7103         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7104     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7105     if(bookHit != NULL && !cps->bookSuspend) {
7106         // make sure opponent is not going to reply after receiving move to book position
7107         SendToProgram("force\n", cps);
7108         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7109     }
7110     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7111     // now arrange restart after book miss
7112     if(bookHit) {
7113         // after a book hit we never send 'go', and the code after the call to this routine
7114         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7115         char buf[MSG_SIZ];
7116         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7117         SendToProgram(buf, cps);
7118         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7119     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7120         SendToProgram("go\n", cps);
7121         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7122     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7123         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7124             SendToProgram("go\n", cps);
7125         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7126     }
7127     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7128 }
7129
7130 char *savedMessage;
7131 ChessProgramState *savedState;
7132 void DeferredBookMove(void)
7133 {
7134         if(savedState->lastPing != savedState->lastPong)
7135                     ScheduleDelayedEvent(DeferredBookMove, 10);
7136         else
7137         HandleMachineMove(savedMessage, savedState);
7138 }
7139
7140 void
7141 HandleMachineMove(message, cps)
7142      char *message;
7143      ChessProgramState *cps;
7144 {
7145     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7146     char realname[MSG_SIZ];
7147     int fromX, fromY, toX, toY;
7148     ChessMove moveType;
7149     char promoChar;
7150     char *p;
7151     int machineWhite;
7152     char *bookHit;
7153
7154     cps->userError = 0;
7155
7156 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7157     /*
7158      * Kludge to ignore BEL characters
7159      */
7160     while (*message == '\007') message++;
7161
7162     /*
7163      * [HGM] engine debug message: ignore lines starting with '#' character
7164      */
7165     if(cps->debug && *message == '#') return;
7166
7167     /*
7168      * Look for book output
7169      */
7170     if (cps == &first && bookRequested) {
7171         if (message[0] == '\t' || message[0] == ' ') {
7172             /* Part of the book output is here; append it */
7173             strcat(bookOutput, message);
7174             strcat(bookOutput, "  \n");
7175             return;
7176         } else if (bookOutput[0] != NULLCHAR) {
7177             /* All of book output has arrived; display it */
7178             char *p = bookOutput;
7179             while (*p != NULLCHAR) {
7180                 if (*p == '\t') *p = ' ';
7181                 p++;
7182             }
7183             DisplayInformation(bookOutput);
7184             bookRequested = FALSE;
7185             /* Fall through to parse the current output */
7186         }
7187     }
7188
7189     /*
7190      * Look for machine move.
7191      */
7192     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7193         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7194     {
7195         /* This method is only useful on engines that support ping */
7196         if (cps->lastPing != cps->lastPong) {
7197           if (gameMode == BeginningOfGame) {
7198             /* Extra move from before last new; ignore */
7199             if (appData.debugMode) {
7200                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7201             }
7202           } else {
7203             if (appData.debugMode) {
7204                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7205                         cps->which, gameMode);
7206             }
7207
7208             SendToProgram("undo\n", cps);
7209           }
7210           return;
7211         }
7212
7213         switch (gameMode) {
7214           case BeginningOfGame:
7215             /* Extra move from before last reset; ignore */
7216             if (appData.debugMode) {
7217                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7218             }
7219             return;
7220
7221           case EndOfGame:
7222           case IcsIdle:
7223           default:
7224             /* Extra move after we tried to stop.  The mode test is
7225                not a reliable way of detecting this problem, but it's
7226                the best we can do on engines that don't support ping.
7227             */
7228             if (appData.debugMode) {
7229                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7230                         cps->which, gameMode);
7231             }
7232             SendToProgram("undo\n", cps);
7233             return;
7234
7235           case MachinePlaysWhite:
7236           case IcsPlayingWhite:
7237             machineWhite = TRUE;
7238             break;
7239
7240           case MachinePlaysBlack:
7241           case IcsPlayingBlack:
7242             machineWhite = FALSE;
7243             break;
7244
7245           case TwoMachinesPlay:
7246             machineWhite = (cps->twoMachinesColor[0] == 'w');
7247             break;
7248         }
7249         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7250             if (appData.debugMode) {
7251                 fprintf(debugFP,
7252                         "Ignoring move out of turn by %s, gameMode %d"
7253                         ", forwardMost %d\n",
7254                         cps->which, gameMode, forwardMostMove);
7255             }
7256             return;
7257         }
7258
7259     if (appData.debugMode) { int f = forwardMostMove;
7260         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7261                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7262                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7263     }
7264         if(cps->alphaRank) AlphaRank(machineMove, 4);
7265         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7266                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7267             /* Machine move could not be parsed; ignore it. */
7268           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7269                     machineMove, cps->which);
7270             DisplayError(buf1, 0);
7271             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7272                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7273             if (gameMode == TwoMachinesPlay) {
7274               GameEnds(machineWhite ? BlackWins : WhiteWins,
7275                        buf1, GE_XBOARD);
7276             }
7277             return;
7278         }
7279
7280         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7281         /* So we have to redo legality test with true e.p. status here,  */
7282         /* to make sure an illegal e.p. capture does not slip through,   */
7283         /* to cause a forfeit on a justified illegal-move complaint      */
7284         /* of the opponent.                                              */
7285         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7286            ChessMove moveType;
7287            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7288                              fromY, fromX, toY, toX, promoChar);
7289             if (appData.debugMode) {
7290                 int i;
7291                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7292                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7293                 fprintf(debugFP, "castling rights\n");
7294             }
7295             if(moveType == IllegalMove) {
7296               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7297                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7298                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7299                            buf1, GE_XBOARD);
7300                 return;
7301            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7302            /* [HGM] Kludge to handle engines that send FRC-style castling
7303               when they shouldn't (like TSCP-Gothic) */
7304            switch(moveType) {
7305              case WhiteASideCastleFR:
7306              case BlackASideCastleFR:
7307                toX+=2;
7308                currentMoveString[2]++;
7309                break;
7310              case WhiteHSideCastleFR:
7311              case BlackHSideCastleFR:
7312                toX--;
7313                currentMoveString[2]--;
7314                break;
7315              default: ; // nothing to do, but suppresses warning of pedantic compilers
7316            }
7317         }
7318         hintRequested = FALSE;
7319         lastHint[0] = NULLCHAR;
7320         bookRequested = FALSE;
7321         /* Program may be pondering now */
7322         cps->maybeThinking = TRUE;
7323         if (cps->sendTime == 2) cps->sendTime = 1;
7324         if (cps->offeredDraw) cps->offeredDraw--;
7325
7326         /* [AS] Save move info*/
7327         pvInfoList[ forwardMostMove ].score = programStats.score;
7328         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7329         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7330
7331         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7332
7333         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7334         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7335             int count = 0;
7336
7337             while( count < adjudicateLossPlies ) {
7338                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7339
7340                 if( count & 1 ) {
7341                     score = -score; /* Flip score for winning side */
7342                 }
7343
7344                 if( score > adjudicateLossThreshold ) {
7345                     break;
7346                 }
7347
7348                 count++;
7349             }
7350
7351             if( count >= adjudicateLossPlies ) {
7352                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7353
7354                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7355                     "Xboard adjudication",
7356                     GE_XBOARD );
7357
7358                 return;
7359             }
7360         }
7361
7362         if(Adjudicate(cps)) {
7363             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7364             return; // [HGM] adjudicate: for all automatic game ends
7365         }
7366
7367 #if ZIPPY
7368         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7369             first.initDone) {
7370           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7371                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7372                 SendToICS("draw ");
7373                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7374           }
7375           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7376           ics_user_moved = 1;
7377           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7378                 char buf[3*MSG_SIZ];
7379
7380                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7381                         programStats.score / 100.,
7382                         programStats.depth,
7383                         programStats.time / 100.,
7384                         (unsigned int)programStats.nodes,
7385                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7386                         programStats.movelist);
7387                 SendToICS(buf);
7388 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7389           }
7390         }
7391 #endif
7392
7393         /* [AS] Clear stats for next move */
7394         ClearProgramStats();
7395         thinkOutput[0] = NULLCHAR;
7396         hiddenThinkOutputState = 0;
7397
7398         bookHit = NULL;
7399         if (gameMode == TwoMachinesPlay) {
7400             /* [HGM] relaying draw offers moved to after reception of move */
7401             /* and interpreting offer as claim if it brings draw condition */
7402             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7403                 SendToProgram("draw\n", cps->other);
7404             }
7405             if (cps->other->sendTime) {
7406                 SendTimeRemaining(cps->other,
7407                                   cps->other->twoMachinesColor[0] == 'w');
7408             }
7409             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7410             if (firstMove && !bookHit) {
7411                 firstMove = FALSE;
7412                 if (cps->other->useColors) {
7413                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7414                 }
7415                 SendToProgram("go\n", cps->other);
7416             }
7417             cps->other->maybeThinking = TRUE;
7418         }
7419
7420         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7421
7422         if (!pausing && appData.ringBellAfterMoves) {
7423             RingBell();
7424         }
7425
7426         /*
7427          * Reenable menu items that were disabled while
7428          * machine was thinking
7429          */
7430         if (gameMode != TwoMachinesPlay)
7431             SetUserThinkingEnables();
7432
7433         // [HGM] book: after book hit opponent has received move and is now in force mode
7434         // force the book reply into it, and then fake that it outputted this move by jumping
7435         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7436         if(bookHit) {
7437                 static char bookMove[MSG_SIZ]; // a bit generous?
7438
7439                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7440                 strcat(bookMove, bookHit);
7441                 message = bookMove;
7442                 cps = cps->other;
7443                 programStats.nodes = programStats.depth = programStats.time =
7444                 programStats.score = programStats.got_only_move = 0;
7445                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7446
7447                 if(cps->lastPing != cps->lastPong) {
7448                     savedMessage = message; // args for deferred call
7449                     savedState = cps;
7450                     ScheduleDelayedEvent(DeferredBookMove, 10);
7451                     return;
7452                 }
7453                 goto FakeBookMove;
7454         }
7455
7456         return;
7457     }
7458
7459     /* Set special modes for chess engines.  Later something general
7460      *  could be added here; for now there is just one kludge feature,
7461      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7462      *  when "xboard" is given as an interactive command.
7463      */
7464     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7465         cps->useSigint = FALSE;
7466         cps->useSigterm = FALSE;
7467     }
7468     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7469       ParseFeatures(message+8, cps);
7470       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7471     }
7472
7473     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7474       int dummy, s=6; char buf[MSG_SIZ];
7475       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7476       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7477       ParseFEN(boards[0], &dummy, message+s);
7478       DrawPosition(TRUE, boards[0]);
7479       startedFromSetupPosition = TRUE;
7480       return;
7481     }
7482     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7483      * want this, I was asked to put it in, and obliged.
7484      */
7485     if (!strncmp(message, "setboard ", 9)) {
7486         Board initial_position;
7487
7488         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7489
7490         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7491             DisplayError(_("Bad FEN received from engine"), 0);
7492             return ;
7493         } else {
7494            Reset(TRUE, FALSE);
7495            CopyBoard(boards[0], initial_position);
7496            initialRulePlies = FENrulePlies;
7497            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7498            else gameMode = MachinePlaysBlack;
7499            DrawPosition(FALSE, boards[currentMove]);
7500         }
7501         return;
7502     }
7503
7504     /*
7505      * Look for communication commands
7506      */
7507     if (!strncmp(message, "telluser ", 9)) {
7508         if(message[9] == '\\' && message[10] == '\\')
7509             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7510         DisplayNote(message + 9);
7511         return;
7512     }
7513     if (!strncmp(message, "tellusererror ", 14)) {
7514         cps->userError = 1;
7515         if(message[14] == '\\' && message[15] == '\\')
7516             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7517         DisplayError(message + 14, 0);
7518         return;
7519     }
7520     if (!strncmp(message, "tellopponent ", 13)) {
7521       if (appData.icsActive) {
7522         if (loggedOn) {
7523           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7524           SendToICS(buf1);
7525         }
7526       } else {
7527         DisplayNote(message + 13);
7528       }
7529       return;
7530     }
7531     if (!strncmp(message, "tellothers ", 11)) {
7532       if (appData.icsActive) {
7533         if (loggedOn) {
7534           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7535           SendToICS(buf1);
7536         }
7537       }
7538       return;
7539     }
7540     if (!strncmp(message, "tellall ", 8)) {
7541       if (appData.icsActive) {
7542         if (loggedOn) {
7543           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7544           SendToICS(buf1);
7545         }
7546       } else {
7547         DisplayNote(message + 8);
7548       }
7549       return;
7550     }
7551     if (strncmp(message, "warning", 7) == 0) {
7552         /* Undocumented feature, use tellusererror in new code */
7553         DisplayError(message, 0);
7554         return;
7555     }
7556     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7557         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7558         strcat(realname, " query");
7559         AskQuestion(realname, buf2, buf1, cps->pr);
7560         return;
7561     }
7562     /* Commands from the engine directly to ICS.  We don't allow these to be
7563      *  sent until we are logged on. Crafty kibitzes have been known to
7564      *  interfere with the login process.
7565      */
7566     if (loggedOn) {
7567         if (!strncmp(message, "tellics ", 8)) {
7568             SendToICS(message + 8);
7569             SendToICS("\n");
7570             return;
7571         }
7572         if (!strncmp(message, "tellicsnoalias ", 15)) {
7573             SendToICS(ics_prefix);
7574             SendToICS(message + 15);
7575             SendToICS("\n");
7576             return;
7577         }
7578         /* The following are for backward compatibility only */
7579         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7580             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7581             SendToICS(ics_prefix);
7582             SendToICS(message);
7583             SendToICS("\n");
7584             return;
7585         }
7586     }
7587     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7588         return;
7589     }
7590     /*
7591      * If the move is illegal, cancel it and redraw the board.
7592      * Also deal with other error cases.  Matching is rather loose
7593      * here to accommodate engines written before the spec.
7594      */
7595     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7596         strncmp(message, "Error", 5) == 0) {
7597         if (StrStr(message, "name") ||
7598             StrStr(message, "rating") || StrStr(message, "?") ||
7599             StrStr(message, "result") || StrStr(message, "board") ||
7600             StrStr(message, "bk") || StrStr(message, "computer") ||
7601             StrStr(message, "variant") || StrStr(message, "hint") ||
7602             StrStr(message, "random") || StrStr(message, "depth") ||
7603             StrStr(message, "accepted")) {
7604             return;
7605         }
7606         if (StrStr(message, "protover")) {
7607           /* Program is responding to input, so it's apparently done
7608              initializing, and this error message indicates it is
7609              protocol version 1.  So we don't need to wait any longer
7610              for it to initialize and send feature commands. */
7611           FeatureDone(cps, 1);
7612           cps->protocolVersion = 1;
7613           return;
7614         }
7615         cps->maybeThinking = FALSE;
7616
7617         if (StrStr(message, "draw")) {
7618             /* Program doesn't have "draw" command */
7619             cps->sendDrawOffers = 0;
7620             return;
7621         }
7622         if (cps->sendTime != 1 &&
7623             (StrStr(message, "time") || StrStr(message, "otim"))) {
7624           /* Program apparently doesn't have "time" or "otim" command */
7625           cps->sendTime = 0;
7626           return;
7627         }
7628         if (StrStr(message, "analyze")) {
7629             cps->analysisSupport = FALSE;
7630             cps->analyzing = FALSE;
7631             Reset(FALSE, TRUE);
7632             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7633             DisplayError(buf2, 0);
7634             return;
7635         }
7636         if (StrStr(message, "(no matching move)st")) {
7637           /* Special kludge for GNU Chess 4 only */
7638           cps->stKludge = TRUE;
7639           SendTimeControl(cps, movesPerSession, timeControl,
7640                           timeIncrement, appData.searchDepth,
7641                           searchTime);
7642           return;
7643         }
7644         if (StrStr(message, "(no matching move)sd")) {
7645           /* Special kludge for GNU Chess 4 only */
7646           cps->sdKludge = TRUE;
7647           SendTimeControl(cps, movesPerSession, timeControl,
7648                           timeIncrement, appData.searchDepth,
7649                           searchTime);
7650           return;
7651         }
7652         if (!StrStr(message, "llegal")) {
7653             return;
7654         }
7655         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7656             gameMode == IcsIdle) return;
7657         if (forwardMostMove <= backwardMostMove) return;
7658         if (pausing) PauseEvent();
7659       if(appData.forceIllegal) {
7660             // [HGM] illegal: machine refused move; force position after move into it
7661           SendToProgram("force\n", cps);
7662           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7663                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7664                 // when black is to move, while there might be nothing on a2 or black
7665                 // might already have the move. So send the board as if white has the move.
7666                 // But first we must change the stm of the engine, as it refused the last move
7667                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7668                 if(WhiteOnMove(forwardMostMove)) {
7669                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7670                     SendBoard(cps, forwardMostMove); // kludgeless board
7671                 } else {
7672                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7673                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7674                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7675                 }
7676           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7677             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7678                  gameMode == TwoMachinesPlay)
7679               SendToProgram("go\n", cps);
7680             return;
7681       } else
7682         if (gameMode == PlayFromGameFile) {
7683             /* Stop reading this game file */
7684             gameMode = EditGame;
7685             ModeHighlight();
7686         }
7687         /* [HGM] illegal-move claim should forfeit game when Xboard */
7688         /* only passes fully legal moves                            */
7689         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7690             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7691                                 "False illegal-move claim", GE_XBOARD );
7692             return; // do not take back move we tested as valid
7693         }
7694         currentMove = forwardMostMove-1;
7695         DisplayMove(currentMove-1); /* before DisplayMoveError */
7696         SwitchClocks(forwardMostMove-1); // [HGM] race
7697         DisplayBothClocks();
7698         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7699                 parseList[currentMove], cps->which);
7700         DisplayMoveError(buf1);
7701         DrawPosition(FALSE, boards[currentMove]);
7702         return;
7703     }
7704     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7705         /* Program has a broken "time" command that
7706            outputs a string not ending in newline.
7707            Don't use it. */
7708         cps->sendTime = 0;
7709     }
7710
7711     /*
7712      * If chess program startup fails, exit with an error message.
7713      * Attempts to recover here are futile.
7714      */
7715     if ((StrStr(message, "unknown host") != NULL)
7716         || (StrStr(message, "No remote directory") != NULL)
7717         || (StrStr(message, "not found") != NULL)
7718         || (StrStr(message, "No such file") != NULL)
7719         || (StrStr(message, "can't alloc") != NULL)
7720         || (StrStr(message, "Permission denied") != NULL)) {
7721
7722         cps->maybeThinking = FALSE;
7723         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7724                 cps->which, cps->program, cps->host, message);
7725         RemoveInputSource(cps->isr);
7726         DisplayFatalError(buf1, 0, 1);
7727         return;
7728     }
7729
7730     /*
7731      * Look for hint output
7732      */
7733     if (sscanf(message, "Hint: %s", buf1) == 1) {
7734         if (cps == &first && hintRequested) {
7735             hintRequested = FALSE;
7736             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7737                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7738                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7739                                     PosFlags(forwardMostMove),
7740                                     fromY, fromX, toY, toX, promoChar, buf1);
7741                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7742                 DisplayInformation(buf2);
7743             } else {
7744                 /* Hint move could not be parsed!? */
7745               snprintf(buf2, sizeof(buf2),
7746                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7747                         buf1, cps->which);
7748                 DisplayError(buf2, 0);
7749             }
7750         } else {
7751           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7752         }
7753         return;
7754     }
7755
7756     /*
7757      * Ignore other messages if game is not in progress
7758      */
7759     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7760         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7761
7762     /*
7763      * look for win, lose, draw, or draw offer
7764      */
7765     if (strncmp(message, "1-0", 3) == 0) {
7766         char *p, *q, *r = "";
7767         p = strchr(message, '{');
7768         if (p) {
7769             q = strchr(p, '}');
7770             if (q) {
7771                 *q = NULLCHAR;
7772                 r = p + 1;
7773             }
7774         }
7775         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7776         return;
7777     } else if (strncmp(message, "0-1", 3) == 0) {
7778         char *p, *q, *r = "";
7779         p = strchr(message, '{');
7780         if (p) {
7781             q = strchr(p, '}');
7782             if (q) {
7783                 *q = NULLCHAR;
7784                 r = p + 1;
7785             }
7786         }
7787         /* Kludge for Arasan 4.1 bug */
7788         if (strcmp(r, "Black resigns") == 0) {
7789             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7790             return;
7791         }
7792         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7793         return;
7794     } else if (strncmp(message, "1/2", 3) == 0) {
7795         char *p, *q, *r = "";
7796         p = strchr(message, '{');
7797         if (p) {
7798             q = strchr(p, '}');
7799             if (q) {
7800                 *q = NULLCHAR;
7801                 r = p + 1;
7802             }
7803         }
7804
7805         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7806         return;
7807
7808     } else if (strncmp(message, "White resign", 12) == 0) {
7809         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7810         return;
7811     } else if (strncmp(message, "Black resign", 12) == 0) {
7812         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7813         return;
7814     } else if (strncmp(message, "White matches", 13) == 0 ||
7815                strncmp(message, "Black matches", 13) == 0   ) {
7816         /* [HGM] ignore GNUShogi noises */
7817         return;
7818     } else if (strncmp(message, "White", 5) == 0 &&
7819                message[5] != '(' &&
7820                StrStr(message, "Black") == NULL) {
7821         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7822         return;
7823     } else if (strncmp(message, "Black", 5) == 0 &&
7824                message[5] != '(') {
7825         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7826         return;
7827     } else if (strcmp(message, "resign") == 0 ||
7828                strcmp(message, "computer resigns") == 0) {
7829         switch (gameMode) {
7830           case MachinePlaysBlack:
7831           case IcsPlayingBlack:
7832             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7833             break;
7834           case MachinePlaysWhite:
7835           case IcsPlayingWhite:
7836             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7837             break;
7838           case TwoMachinesPlay:
7839             if (cps->twoMachinesColor[0] == 'w')
7840               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7841             else
7842               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7843             break;
7844           default:
7845             /* can't happen */
7846             break;
7847         }
7848         return;
7849     } else if (strncmp(message, "opponent mates", 14) == 0) {
7850         switch (gameMode) {
7851           case MachinePlaysBlack:
7852           case IcsPlayingBlack:
7853             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7854             break;
7855           case MachinePlaysWhite:
7856           case IcsPlayingWhite:
7857             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7858             break;
7859           case TwoMachinesPlay:
7860             if (cps->twoMachinesColor[0] == 'w')
7861               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7862             else
7863               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7864             break;
7865           default:
7866             /* can't happen */
7867             break;
7868         }
7869         return;
7870     } else if (strncmp(message, "computer mates", 14) == 0) {
7871         switch (gameMode) {
7872           case MachinePlaysBlack:
7873           case IcsPlayingBlack:
7874             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7875             break;
7876           case MachinePlaysWhite:
7877           case IcsPlayingWhite:
7878             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7879             break;
7880           case TwoMachinesPlay:
7881             if (cps->twoMachinesColor[0] == 'w')
7882               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7883             else
7884               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7885             break;
7886           default:
7887             /* can't happen */
7888             break;
7889         }
7890         return;
7891     } else if (strncmp(message, "checkmate", 9) == 0) {
7892         if (WhiteOnMove(forwardMostMove)) {
7893             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7894         } else {
7895             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7896         }
7897         return;
7898     } else if (strstr(message, "Draw") != NULL ||
7899                strstr(message, "game is a draw") != NULL) {
7900         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7901         return;
7902     } else if (strstr(message, "offer") != NULL &&
7903                strstr(message, "draw") != NULL) {
7904 #if ZIPPY
7905         if (appData.zippyPlay && first.initDone) {
7906             /* Relay offer to ICS */
7907             SendToICS(ics_prefix);
7908             SendToICS("draw\n");
7909         }
7910 #endif
7911         cps->offeredDraw = 2; /* valid until this engine moves twice */
7912         if (gameMode == TwoMachinesPlay) {
7913             if (cps->other->offeredDraw) {
7914                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7915             /* [HGM] in two-machine mode we delay relaying draw offer      */
7916             /* until after we also have move, to see if it is really claim */
7917             }
7918         } else if (gameMode == MachinePlaysWhite ||
7919                    gameMode == MachinePlaysBlack) {
7920           if (userOfferedDraw) {
7921             DisplayInformation(_("Machine accepts your draw offer"));
7922             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7923           } else {
7924             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7925           }
7926         }
7927     }
7928
7929
7930     /*
7931      * Look for thinking output
7932      */
7933     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7934           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7935                                 ) {
7936         int plylev, mvleft, mvtot, curscore, time;
7937         char mvname[MOVE_LEN];
7938         u64 nodes; // [DM]
7939         char plyext;
7940         int ignore = FALSE;
7941         int prefixHint = FALSE;
7942         mvname[0] = NULLCHAR;
7943
7944         switch (gameMode) {
7945           case MachinePlaysBlack:
7946           case IcsPlayingBlack:
7947             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7948             break;
7949           case MachinePlaysWhite:
7950           case IcsPlayingWhite:
7951             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7952             break;
7953           case AnalyzeMode:
7954           case AnalyzeFile:
7955             break;
7956           case IcsObserving: /* [DM] icsEngineAnalyze */
7957             if (!appData.icsEngineAnalyze) ignore = TRUE;
7958             break;
7959           case TwoMachinesPlay:
7960             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7961                 ignore = TRUE;
7962             }
7963             break;
7964           default:
7965             ignore = TRUE;
7966             break;
7967         }
7968
7969         if (!ignore) {
7970             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7971             buf1[0] = NULLCHAR;
7972             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7973                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7974
7975                 if (plyext != ' ' && plyext != '\t') {
7976                     time *= 100;
7977                 }
7978
7979                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7980                 if( cps->scoreIsAbsolute &&
7981                     ( gameMode == MachinePlaysBlack ||
7982                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7983                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7984                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7985                      !WhiteOnMove(currentMove)
7986                     ) )
7987                 {
7988                     curscore = -curscore;
7989                 }
7990
7991
7992                 tempStats.depth = plylev;
7993                 tempStats.nodes = nodes;
7994                 tempStats.time = time;
7995                 tempStats.score = curscore;
7996                 tempStats.got_only_move = 0;
7997
7998                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7999                         int ticklen;
8000
8001                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8002                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8003                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8004                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8005                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8006                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8007                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8008                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8009                 }
8010
8011                 /* Buffer overflow protection */
8012                 if (buf1[0] != NULLCHAR) {
8013                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8014                         && appData.debugMode) {
8015                         fprintf(debugFP,
8016                                 "PV is too long; using the first %u bytes.\n",
8017                                 (unsigned) sizeof(tempStats.movelist) - 1);
8018                     }
8019
8020                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8021                 } else {
8022                     sprintf(tempStats.movelist, " no PV\n");
8023                 }
8024
8025                 if (tempStats.seen_stat) {
8026                     tempStats.ok_to_send = 1;
8027                 }
8028
8029                 if (strchr(tempStats.movelist, '(') != NULL) {
8030                     tempStats.line_is_book = 1;
8031                     tempStats.nr_moves = 0;
8032                     tempStats.moves_left = 0;
8033                 } else {
8034                     tempStats.line_is_book = 0;
8035                 }
8036
8037                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8038                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8039
8040                 SendProgramStatsToFrontend( cps, &tempStats );
8041
8042                 /*
8043                     [AS] Protect the thinkOutput buffer from overflow... this
8044                     is only useful if buf1 hasn't overflowed first!
8045                 */
8046                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8047                          plylev,
8048                          (gameMode == TwoMachinesPlay ?
8049                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8050                          ((double) curscore) / 100.0,
8051                          prefixHint ? lastHint : "",
8052                          prefixHint ? " " : "" );
8053
8054                 if( buf1[0] != NULLCHAR ) {
8055                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8056
8057                     if( strlen(buf1) > max_len ) {
8058                         if( appData.debugMode) {
8059                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8060                         }
8061                         buf1[max_len+1] = '\0';
8062                     }
8063
8064                     strcat( thinkOutput, buf1 );
8065                 }
8066
8067                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8068                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8069                     DisplayMove(currentMove - 1);
8070                 }
8071                 return;
8072
8073             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8074                 /* crafty (9.25+) says "(only move) <move>"
8075                  * if there is only 1 legal move
8076                  */
8077                 sscanf(p, "(only move) %s", buf1);
8078                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8079                 sprintf(programStats.movelist, "%s (only move)", buf1);
8080                 programStats.depth = 1;
8081                 programStats.nr_moves = 1;
8082                 programStats.moves_left = 1;
8083                 programStats.nodes = 1;
8084                 programStats.time = 1;
8085                 programStats.got_only_move = 1;
8086
8087                 /* Not really, but we also use this member to
8088                    mean "line isn't going to change" (Crafty
8089                    isn't searching, so stats won't change) */
8090                 programStats.line_is_book = 1;
8091
8092                 SendProgramStatsToFrontend( cps, &programStats );
8093
8094                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8095                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8096                     DisplayMove(currentMove - 1);
8097                 }
8098                 return;
8099             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8100                               &time, &nodes, &plylev, &mvleft,
8101                               &mvtot, mvname) >= 5) {
8102                 /* The stat01: line is from Crafty (9.29+) in response
8103                    to the "." command */
8104                 programStats.seen_stat = 1;
8105                 cps->maybeThinking = TRUE;
8106
8107                 if (programStats.got_only_move || !appData.periodicUpdates)
8108                   return;
8109
8110                 programStats.depth = plylev;
8111                 programStats.time = time;
8112                 programStats.nodes = nodes;
8113                 programStats.moves_left = mvleft;
8114                 programStats.nr_moves = mvtot;
8115                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8116                 programStats.ok_to_send = 1;
8117                 programStats.movelist[0] = '\0';
8118
8119                 SendProgramStatsToFrontend( cps, &programStats );
8120
8121                 return;
8122
8123             } else if (strncmp(message,"++",2) == 0) {
8124                 /* Crafty 9.29+ outputs this */
8125                 programStats.got_fail = 2;
8126                 return;
8127
8128             } else if (strncmp(message,"--",2) == 0) {
8129                 /* Crafty 9.29+ outputs this */
8130                 programStats.got_fail = 1;
8131                 return;
8132
8133             } else if (thinkOutput[0] != NULLCHAR &&
8134                        strncmp(message, "    ", 4) == 0) {
8135                 unsigned message_len;
8136
8137                 p = message;
8138                 while (*p && *p == ' ') p++;
8139
8140                 message_len = strlen( p );
8141
8142                 /* [AS] Avoid buffer overflow */
8143                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8144                     strcat(thinkOutput, " ");
8145                     strcat(thinkOutput, p);
8146                 }
8147
8148                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8149                     strcat(programStats.movelist, " ");
8150                     strcat(programStats.movelist, p);
8151                 }
8152
8153                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8154                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8155                     DisplayMove(currentMove - 1);
8156                 }
8157                 return;
8158             }
8159         }
8160         else {
8161             buf1[0] = NULLCHAR;
8162
8163             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8164                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8165             {
8166                 ChessProgramStats cpstats;
8167
8168                 if (plyext != ' ' && plyext != '\t') {
8169                     time *= 100;
8170                 }
8171
8172                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8173                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8174                     curscore = -curscore;
8175                 }
8176
8177                 cpstats.depth = plylev;
8178                 cpstats.nodes = nodes;
8179                 cpstats.time = time;
8180                 cpstats.score = curscore;
8181                 cpstats.got_only_move = 0;
8182                 cpstats.movelist[0] = '\0';
8183
8184                 if (buf1[0] != NULLCHAR) {
8185                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8186                 }
8187
8188                 cpstats.ok_to_send = 0;
8189                 cpstats.line_is_book = 0;
8190                 cpstats.nr_moves = 0;
8191                 cpstats.moves_left = 0;
8192
8193                 SendProgramStatsToFrontend( cps, &cpstats );
8194             }
8195         }
8196     }
8197 }
8198
8199
8200 /* Parse a game score from the character string "game", and
8201    record it as the history of the current game.  The game
8202    score is NOT assumed to start from the standard position.
8203    The display is not updated in any way.
8204    */
8205 void
8206 ParseGameHistory(game)
8207      char *game;
8208 {
8209     ChessMove moveType;
8210     int fromX, fromY, toX, toY, boardIndex;
8211     char promoChar;
8212     char *p, *q;
8213     char buf[MSG_SIZ];
8214
8215     if (appData.debugMode)
8216       fprintf(debugFP, "Parsing game history: %s\n", game);
8217
8218     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8219     gameInfo.site = StrSave(appData.icsHost);
8220     gameInfo.date = PGNDate();
8221     gameInfo.round = StrSave("-");
8222
8223     /* Parse out names of players */
8224     while (*game == ' ') game++;
8225     p = buf;
8226     while (*game != ' ') *p++ = *game++;
8227     *p = NULLCHAR;
8228     gameInfo.white = StrSave(buf);
8229     while (*game == ' ') game++;
8230     p = buf;
8231     while (*game != ' ' && *game != '\n') *p++ = *game++;
8232     *p = NULLCHAR;
8233     gameInfo.black = StrSave(buf);
8234
8235     /* Parse moves */
8236     boardIndex = blackPlaysFirst ? 1 : 0;
8237     yynewstr(game);
8238     for (;;) {
8239         yyboardindex = boardIndex;
8240         moveType = (ChessMove) Myylex();
8241         switch (moveType) {
8242           case IllegalMove:             /* maybe suicide chess, etc. */
8243   if (appData.debugMode) {
8244     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8245     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8246     setbuf(debugFP, NULL);
8247   }
8248           case WhitePromotion:
8249           case BlackPromotion:
8250           case WhiteNonPromotion:
8251           case BlackNonPromotion:
8252           case NormalMove:
8253           case WhiteCapturesEnPassant:
8254           case BlackCapturesEnPassant:
8255           case WhiteKingSideCastle:
8256           case WhiteQueenSideCastle:
8257           case BlackKingSideCastle:
8258           case BlackQueenSideCastle:
8259           case WhiteKingSideCastleWild:
8260           case WhiteQueenSideCastleWild:
8261           case BlackKingSideCastleWild:
8262           case BlackQueenSideCastleWild:
8263           /* PUSH Fabien */
8264           case WhiteHSideCastleFR:
8265           case WhiteASideCastleFR:
8266           case BlackHSideCastleFR:
8267           case BlackASideCastleFR:
8268           /* POP Fabien */
8269             fromX = currentMoveString[0] - AAA;
8270             fromY = currentMoveString[1] - ONE;
8271             toX = currentMoveString[2] - AAA;
8272             toY = currentMoveString[3] - ONE;
8273             promoChar = currentMoveString[4];
8274             break;
8275           case WhiteDrop:
8276           case BlackDrop:
8277             fromX = moveType == WhiteDrop ?
8278               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8279             (int) CharToPiece(ToLower(currentMoveString[0]));
8280             fromY = DROP_RANK;
8281             toX = currentMoveString[2] - AAA;
8282             toY = currentMoveString[3] - ONE;
8283             promoChar = NULLCHAR;
8284             break;
8285           case AmbiguousMove:
8286             /* bug? */
8287             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8288   if (appData.debugMode) {
8289     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8290     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8291     setbuf(debugFP, NULL);
8292   }
8293             DisplayError(buf, 0);
8294             return;
8295           case ImpossibleMove:
8296             /* bug? */
8297             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8298   if (appData.debugMode) {
8299     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8300     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8301     setbuf(debugFP, NULL);
8302   }
8303             DisplayError(buf, 0);
8304             return;
8305           case EndOfFile:
8306             if (boardIndex < backwardMostMove) {
8307                 /* Oops, gap.  How did that happen? */
8308                 DisplayError(_("Gap in move list"), 0);
8309                 return;
8310             }
8311             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8312             if (boardIndex > forwardMostMove) {
8313                 forwardMostMove = boardIndex;
8314             }
8315             return;
8316           case ElapsedTime:
8317             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8318                 strcat(parseList[boardIndex-1], " ");
8319                 strcat(parseList[boardIndex-1], yy_text);
8320             }
8321             continue;
8322           case Comment:
8323           case PGNTag:
8324           case NAG:
8325           default:
8326             /* ignore */
8327             continue;
8328           case WhiteWins:
8329           case BlackWins:
8330           case GameIsDrawn:
8331           case GameUnfinished:
8332             if (gameMode == IcsExamining) {
8333                 if (boardIndex < backwardMostMove) {
8334                     /* Oops, gap.  How did that happen? */
8335                     return;
8336                 }
8337                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8338                 return;
8339             }
8340             gameInfo.result = moveType;
8341             p = strchr(yy_text, '{');
8342             if (p == NULL) p = strchr(yy_text, '(');
8343             if (p == NULL) {
8344                 p = yy_text;
8345                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8346             } else {
8347                 q = strchr(p, *p == '{' ? '}' : ')');
8348                 if (q != NULL) *q = NULLCHAR;
8349                 p++;
8350             }
8351             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8352             gameInfo.resultDetails = StrSave(p);
8353             continue;
8354         }
8355         if (boardIndex >= forwardMostMove &&
8356             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8357             backwardMostMove = blackPlaysFirst ? 1 : 0;
8358             return;
8359         }
8360         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8361                                  fromY, fromX, toY, toX, promoChar,
8362                                  parseList[boardIndex]);
8363         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8364         /* currentMoveString is set as a side-effect of yylex */
8365         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8366         strcat(moveList[boardIndex], "\n");
8367         boardIndex++;
8368         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8369         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8370           case MT_NONE:
8371           case MT_STALEMATE:
8372           default:
8373             break;
8374           case MT_CHECK:
8375             if(gameInfo.variant != VariantShogi)
8376                 strcat(parseList[boardIndex - 1], "+");
8377             break;
8378           case MT_CHECKMATE:
8379           case MT_STAINMATE:
8380             strcat(parseList[boardIndex - 1], "#");
8381             break;
8382         }
8383     }
8384 }
8385
8386
8387 /* Apply a move to the given board  */
8388 void
8389 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8390      int fromX, fromY, toX, toY;
8391      int promoChar;
8392      Board board;
8393 {
8394   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8395   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8396
8397     /* [HGM] compute & store e.p. status and castling rights for new position */
8398     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8399
8400       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8401       oldEP = (signed char)board[EP_STATUS];
8402       board[EP_STATUS] = EP_NONE;
8403
8404       if( board[toY][toX] != EmptySquare )
8405            board[EP_STATUS] = EP_CAPTURE;
8406
8407   if (fromY == DROP_RANK) {
8408         /* must be first */
8409         piece = board[toY][toX] = (ChessSquare) fromX;
8410   } else {
8411       int i;
8412
8413       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8414            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8415                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8416       } else
8417       if( board[fromY][fromX] == WhitePawn ) {
8418            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8419                board[EP_STATUS] = EP_PAWN_MOVE;
8420            if( toY-fromY==2) {
8421                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8422                         gameInfo.variant != VariantBerolina || toX < fromX)
8423                       board[EP_STATUS] = toX | berolina;
8424                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8425                         gameInfo.variant != VariantBerolina || toX > fromX)
8426                       board[EP_STATUS] = toX;
8427            }
8428       } else
8429       if( board[fromY][fromX] == BlackPawn ) {
8430            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8431                board[EP_STATUS] = EP_PAWN_MOVE;
8432            if( toY-fromY== -2) {
8433                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8434                         gameInfo.variant != VariantBerolina || toX < fromX)
8435                       board[EP_STATUS] = toX | berolina;
8436                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8437                         gameInfo.variant != VariantBerolina || toX > fromX)
8438                       board[EP_STATUS] = toX;
8439            }
8440        }
8441
8442        for(i=0; i<nrCastlingRights; i++) {
8443            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8444               board[CASTLING][i] == toX   && castlingRank[i] == toY
8445              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8446        }
8447
8448      if (fromX == toX && fromY == toY) return;
8449
8450      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8451      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8452      if(gameInfo.variant == VariantKnightmate)
8453          king += (int) WhiteUnicorn - (int) WhiteKing;
8454
8455     /* Code added by Tord: */
8456     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8457     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8458         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8459       board[fromY][fromX] = EmptySquare;
8460       board[toY][toX] = EmptySquare;
8461       if((toX > fromX) != (piece == WhiteRook)) {
8462         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8463       } else {
8464         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8465       }
8466     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8467                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8468       board[fromY][fromX] = EmptySquare;
8469       board[toY][toX] = EmptySquare;
8470       if((toX > fromX) != (piece == BlackRook)) {
8471         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8472       } else {
8473         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8474       }
8475     /* End of code added by Tord */
8476
8477     } else if (board[fromY][fromX] == king
8478         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8479         && toY == fromY && toX > fromX+1) {
8480         board[fromY][fromX] = EmptySquare;
8481         board[toY][toX] = king;
8482         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8483         board[fromY][BOARD_RGHT-1] = EmptySquare;
8484     } else if (board[fromY][fromX] == king
8485         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8486                && toY == fromY && toX < fromX-1) {
8487         board[fromY][fromX] = EmptySquare;
8488         board[toY][toX] = king;
8489         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8490         board[fromY][BOARD_LEFT] = EmptySquare;
8491     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8492                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8493                && toY >= BOARD_HEIGHT-promoRank
8494                ) {
8495         /* white pawn promotion */
8496         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8497         if (board[toY][toX] == EmptySquare) {
8498             board[toY][toX] = WhiteQueen;
8499         }
8500         if(gameInfo.variant==VariantBughouse ||
8501            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8502             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8503         board[fromY][fromX] = EmptySquare;
8504     } else if ((fromY == BOARD_HEIGHT-4)
8505                && (toX != fromX)
8506                && gameInfo.variant != VariantXiangqi
8507                && gameInfo.variant != VariantBerolina
8508                && (board[fromY][fromX] == WhitePawn)
8509                && (board[toY][toX] == EmptySquare)) {
8510         board[fromY][fromX] = EmptySquare;
8511         board[toY][toX] = WhitePawn;
8512         captured = board[toY - 1][toX];
8513         board[toY - 1][toX] = EmptySquare;
8514     } else if ((fromY == BOARD_HEIGHT-4)
8515                && (toX == fromX)
8516                && gameInfo.variant == VariantBerolina
8517                && (board[fromY][fromX] == WhitePawn)
8518                && (board[toY][toX] == EmptySquare)) {
8519         board[fromY][fromX] = EmptySquare;
8520         board[toY][toX] = WhitePawn;
8521         if(oldEP & EP_BEROLIN_A) {
8522                 captured = board[fromY][fromX-1];
8523                 board[fromY][fromX-1] = EmptySquare;
8524         }else{  captured = board[fromY][fromX+1];
8525                 board[fromY][fromX+1] = EmptySquare;
8526         }
8527     } else if (board[fromY][fromX] == king
8528         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8529                && toY == fromY && toX > fromX+1) {
8530         board[fromY][fromX] = EmptySquare;
8531         board[toY][toX] = king;
8532         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8533         board[fromY][BOARD_RGHT-1] = EmptySquare;
8534     } else if (board[fromY][fromX] == king
8535         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8536                && toY == fromY && toX < fromX-1) {
8537         board[fromY][fromX] = EmptySquare;
8538         board[toY][toX] = king;
8539         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8540         board[fromY][BOARD_LEFT] = EmptySquare;
8541     } else if (fromY == 7 && fromX == 3
8542                && board[fromY][fromX] == BlackKing
8543                && toY == 7 && toX == 5) {
8544         board[fromY][fromX] = EmptySquare;
8545         board[toY][toX] = BlackKing;
8546         board[fromY][7] = EmptySquare;
8547         board[toY][4] = BlackRook;
8548     } else if (fromY == 7 && fromX == 3
8549                && board[fromY][fromX] == BlackKing
8550                && toY == 7 && toX == 1) {
8551         board[fromY][fromX] = EmptySquare;
8552         board[toY][toX] = BlackKing;
8553         board[fromY][0] = EmptySquare;
8554         board[toY][2] = BlackRook;
8555     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8556                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8557                && toY < promoRank
8558                ) {
8559         /* black pawn promotion */
8560         board[toY][toX] = CharToPiece(ToLower(promoChar));
8561         if (board[toY][toX] == EmptySquare) {
8562             board[toY][toX] = BlackQueen;
8563         }
8564         if(gameInfo.variant==VariantBughouse ||
8565            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8566             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8567         board[fromY][fromX] = EmptySquare;
8568     } else if ((fromY == 3)
8569                && (toX != fromX)
8570                && gameInfo.variant != VariantXiangqi
8571                && gameInfo.variant != VariantBerolina
8572                && (board[fromY][fromX] == BlackPawn)
8573                && (board[toY][toX] == EmptySquare)) {
8574         board[fromY][fromX] = EmptySquare;
8575         board[toY][toX] = BlackPawn;
8576         captured = board[toY + 1][toX];
8577         board[toY + 1][toX] = EmptySquare;
8578     } else if ((fromY == 3)
8579                && (toX == fromX)
8580                && gameInfo.variant == VariantBerolina
8581                && (board[fromY][fromX] == BlackPawn)
8582                && (board[toY][toX] == EmptySquare)) {
8583         board[fromY][fromX] = EmptySquare;
8584         board[toY][toX] = BlackPawn;
8585         if(oldEP & EP_BEROLIN_A) {
8586                 captured = board[fromY][fromX-1];
8587                 board[fromY][fromX-1] = EmptySquare;
8588         }else{  captured = board[fromY][fromX+1];
8589                 board[fromY][fromX+1] = EmptySquare;
8590         }
8591     } else {
8592         board[toY][toX] = board[fromY][fromX];
8593         board[fromY][fromX] = EmptySquare;
8594     }
8595   }
8596
8597     if (gameInfo.holdingsWidth != 0) {
8598
8599       /* !!A lot more code needs to be written to support holdings  */
8600       /* [HGM] OK, so I have written it. Holdings are stored in the */
8601       /* penultimate board files, so they are automaticlly stored   */
8602       /* in the game history.                                       */
8603       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8604                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8605         /* Delete from holdings, by decreasing count */
8606         /* and erasing image if necessary            */
8607         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8608         if(p < (int) BlackPawn) { /* white drop */
8609              p -= (int)WhitePawn;
8610                  p = PieceToNumber((ChessSquare)p);
8611              if(p >= gameInfo.holdingsSize) p = 0;
8612              if(--board[p][BOARD_WIDTH-2] <= 0)
8613                   board[p][BOARD_WIDTH-1] = EmptySquare;
8614              if((int)board[p][BOARD_WIDTH-2] < 0)
8615                         board[p][BOARD_WIDTH-2] = 0;
8616         } else {                  /* black drop */
8617              p -= (int)BlackPawn;
8618                  p = PieceToNumber((ChessSquare)p);
8619              if(p >= gameInfo.holdingsSize) p = 0;
8620              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8621                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8622              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8623                         board[BOARD_HEIGHT-1-p][1] = 0;
8624         }
8625       }
8626       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8627           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8628         /* [HGM] holdings: Add to holdings, if holdings exist */
8629         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8630                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8631                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8632         }
8633         p = (int) captured;
8634         if (p >= (int) BlackPawn) {
8635           p -= (int)BlackPawn;
8636           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8637                   /* in Shogi restore piece to its original  first */
8638                   captured = (ChessSquare) (DEMOTED captured);
8639                   p = DEMOTED p;
8640           }
8641           p = PieceToNumber((ChessSquare)p);
8642           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8643           board[p][BOARD_WIDTH-2]++;
8644           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8645         } else {
8646           p -= (int)WhitePawn;
8647           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8648                   captured = (ChessSquare) (DEMOTED captured);
8649                   p = DEMOTED p;
8650           }
8651           p = PieceToNumber((ChessSquare)p);
8652           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8653           board[BOARD_HEIGHT-1-p][1]++;
8654           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8655         }
8656       }
8657     } else if (gameInfo.variant == VariantAtomic) {
8658       if (captured != EmptySquare) {
8659         int y, x;
8660         for (y = toY-1; y <= toY+1; y++) {
8661           for (x = toX-1; x <= toX+1; x++) {
8662             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8663                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8664               board[y][x] = EmptySquare;
8665             }
8666           }
8667         }
8668         board[toY][toX] = EmptySquare;
8669       }
8670     }
8671     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8672         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8673     } else
8674     if(promoChar == '+') {
8675         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8676         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8677     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8678         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8679     }
8680     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8681                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8682         // [HGM] superchess: take promotion piece out of holdings
8683         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8684         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8685             if(!--board[k][BOARD_WIDTH-2])
8686                 board[k][BOARD_WIDTH-1] = EmptySquare;
8687         } else {
8688             if(!--board[BOARD_HEIGHT-1-k][1])
8689                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8690         }
8691     }
8692
8693 }
8694
8695 /* Updates forwardMostMove */
8696 void
8697 MakeMove(fromX, fromY, toX, toY, promoChar)
8698      int fromX, fromY, toX, toY;
8699      int promoChar;
8700 {
8701 //    forwardMostMove++; // [HGM] bare: moved downstream
8702
8703     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8704         int timeLeft; static int lastLoadFlag=0; int king, piece;
8705         piece = boards[forwardMostMove][fromY][fromX];
8706         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8707         if(gameInfo.variant == VariantKnightmate)
8708             king += (int) WhiteUnicorn - (int) WhiteKing;
8709         if(forwardMostMove == 0) {
8710             if(blackPlaysFirst)
8711                 fprintf(serverMoves, "%s;", second.tidy);
8712             fprintf(serverMoves, "%s;", first.tidy);
8713             if(!blackPlaysFirst)
8714                 fprintf(serverMoves, "%s;", second.tidy);
8715         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8716         lastLoadFlag = loadFlag;
8717         // print base move
8718         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8719         // print castling suffix
8720         if( toY == fromY && piece == king ) {
8721             if(toX-fromX > 1)
8722                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8723             if(fromX-toX >1)
8724                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8725         }
8726         // e.p. suffix
8727         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8728              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8729              boards[forwardMostMove][toY][toX] == EmptySquare
8730              && fromX != toX && fromY != toY)
8731                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8732         // promotion suffix
8733         if(promoChar != NULLCHAR)
8734                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8735         if(!loadFlag) {
8736             fprintf(serverMoves, "/%d/%d",
8737                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8738             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8739             else                      timeLeft = blackTimeRemaining/1000;
8740             fprintf(serverMoves, "/%d", timeLeft);
8741         }
8742         fflush(serverMoves);
8743     }
8744
8745     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8746       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8747                         0, 1);
8748       return;
8749     }
8750     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8751     if (commentList[forwardMostMove+1] != NULL) {
8752         free(commentList[forwardMostMove+1]);
8753         commentList[forwardMostMove+1] = NULL;
8754     }
8755     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8756     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8757     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8758     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8759     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8760     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8761     gameInfo.result = GameUnfinished;
8762     if (gameInfo.resultDetails != NULL) {
8763         free(gameInfo.resultDetails);
8764         gameInfo.resultDetails = NULL;
8765     }
8766     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8767                               moveList[forwardMostMove - 1]);
8768     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8769                              PosFlags(forwardMostMove - 1),
8770                              fromY, fromX, toY, toX, promoChar,
8771                              parseList[forwardMostMove - 1]);
8772     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8773       case MT_NONE:
8774       case MT_STALEMATE:
8775       default:
8776         break;
8777       case MT_CHECK:
8778         if(gameInfo.variant != VariantShogi)
8779             strcat(parseList[forwardMostMove - 1], "+");
8780         break;
8781       case MT_CHECKMATE:
8782       case MT_STAINMATE:
8783         strcat(parseList[forwardMostMove - 1], "#");
8784         break;
8785     }
8786     if (appData.debugMode) {
8787         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8788     }
8789
8790 }
8791
8792 /* Updates currentMove if not pausing */
8793 void
8794 ShowMove(fromX, fromY, toX, toY)
8795 {
8796     int instant = (gameMode == PlayFromGameFile) ?
8797         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8798     if(appData.noGUI) return;
8799     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8800         if (!instant) {
8801             if (forwardMostMove == currentMove + 1) {
8802                 AnimateMove(boards[forwardMostMove - 1],
8803                             fromX, fromY, toX, toY);
8804             }
8805             if (appData.highlightLastMove) {
8806                 SetHighlights(fromX, fromY, toX, toY);
8807             }
8808         }
8809         currentMove = forwardMostMove;
8810     }
8811
8812     if (instant) return;
8813
8814     DisplayMove(currentMove - 1);
8815     DrawPosition(FALSE, boards[currentMove]);
8816     DisplayBothClocks();
8817     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8818 }
8819
8820 void SendEgtPath(ChessProgramState *cps)
8821 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8822         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8823
8824         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8825
8826         while(*p) {
8827             char c, *q = name+1, *r, *s;
8828
8829             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8830             while(*p && *p != ',') *q++ = *p++;
8831             *q++ = ':'; *q = 0;
8832             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8833                 strcmp(name, ",nalimov:") == 0 ) {
8834                 // take nalimov path from the menu-changeable option first, if it is defined
8835               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8836                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8837             } else
8838             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8839                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8840                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8841                 s = r = StrStr(s, ":") + 1; // beginning of path info
8842                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8843                 c = *r; *r = 0;             // temporarily null-terminate path info
8844                     *--q = 0;               // strip of trailig ':' from name
8845                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8846                 *r = c;
8847                 SendToProgram(buf,cps);     // send egtbpath command for this format
8848             }
8849             if(*p == ',') p++; // read away comma to position for next format name
8850         }
8851 }
8852
8853 void
8854 InitChessProgram(cps, setup)
8855      ChessProgramState *cps;
8856      int setup; /* [HGM] needed to setup FRC opening position */
8857 {
8858     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8859     if (appData.noChessProgram) return;
8860     hintRequested = FALSE;
8861     bookRequested = FALSE;
8862
8863     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8864     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8865     if(cps->memSize) { /* [HGM] memory */
8866       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8867         SendToProgram(buf, cps);
8868     }
8869     SendEgtPath(cps); /* [HGM] EGT */
8870     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8871       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8872         SendToProgram(buf, cps);
8873     }
8874
8875     SendToProgram(cps->initString, cps);
8876     if (gameInfo.variant != VariantNormal &&
8877         gameInfo.variant != VariantLoadable
8878         /* [HGM] also send variant if board size non-standard */
8879         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8880                                             ) {
8881       char *v = VariantName(gameInfo.variant);
8882       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8883         /* [HGM] in protocol 1 we have to assume all variants valid */
8884         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8885         DisplayFatalError(buf, 0, 1);
8886         return;
8887       }
8888
8889       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8890       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8891       if( gameInfo.variant == VariantXiangqi )
8892            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8893       if( gameInfo.variant == VariantShogi )
8894            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8895       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8896            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8897       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8898           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
8899            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8900       if( gameInfo.variant == VariantCourier )
8901            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8902       if( gameInfo.variant == VariantSuper )
8903            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8904       if( gameInfo.variant == VariantGreat )
8905            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8906       if( gameInfo.variant == VariantSChess )
8907            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8908
8909       if(overruled) {
8910         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8911                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8912            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8913            if(StrStr(cps->variants, b) == NULL) {
8914                // specific sized variant not known, check if general sizing allowed
8915                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8916                    if(StrStr(cps->variants, "boardsize") == NULL) {
8917                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8918                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8919                        DisplayFatalError(buf, 0, 1);
8920                        return;
8921                    }
8922                    /* [HGM] here we really should compare with the maximum supported board size */
8923                }
8924            }
8925       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8926       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8927       SendToProgram(buf, cps);
8928     }
8929     currentlyInitializedVariant = gameInfo.variant;
8930
8931     /* [HGM] send opening position in FRC to first engine */
8932     if(setup) {
8933           SendToProgram("force\n", cps);
8934           SendBoard(cps, 0);
8935           /* engine is now in force mode! Set flag to wake it up after first move. */
8936           setboardSpoiledMachineBlack = 1;
8937     }
8938
8939     if (cps->sendICS) {
8940       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8941       SendToProgram(buf, cps);
8942     }
8943     cps->maybeThinking = FALSE;
8944     cps->offeredDraw = 0;
8945     if (!appData.icsActive) {
8946         SendTimeControl(cps, movesPerSession, timeControl,
8947                         timeIncrement, appData.searchDepth,
8948                         searchTime);
8949     }
8950     if (appData.showThinking
8951         // [HGM] thinking: four options require thinking output to be sent
8952         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8953                                 ) {
8954         SendToProgram("post\n", cps);
8955     }
8956     SendToProgram("hard\n", cps);
8957     if (!appData.ponderNextMove) {
8958         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8959            it without being sure what state we are in first.  "hard"
8960            is not a toggle, so that one is OK.
8961          */
8962         SendToProgram("easy\n", cps);
8963     }
8964     if (cps->usePing) {
8965       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8966       SendToProgram(buf, cps);
8967     }
8968     cps->initDone = TRUE;
8969 }
8970
8971
8972 void
8973 StartChessProgram(cps)
8974      ChessProgramState *cps;
8975 {
8976     char buf[MSG_SIZ];
8977     int err;
8978
8979     if (appData.noChessProgram) return;
8980     cps->initDone = FALSE;
8981
8982     if (strcmp(cps->host, "localhost") == 0) {
8983         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8984     } else if (*appData.remoteShell == NULLCHAR) {
8985         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8986     } else {
8987         if (*appData.remoteUser == NULLCHAR) {
8988           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8989                     cps->program);
8990         } else {
8991           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8992                     cps->host, appData.remoteUser, cps->program);
8993         }
8994         err = StartChildProcess(buf, "", &cps->pr);
8995     }
8996
8997     if (err != 0) {
8998       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8999         DisplayFatalError(buf, err, 1);
9000         cps->pr = NoProc;
9001         cps->isr = NULL;
9002         return;
9003     }
9004
9005     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9006     if (cps->protocolVersion > 1) {
9007       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9008       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9009       cps->comboCnt = 0;  //                and values of combo boxes
9010       SendToProgram(buf, cps);
9011     } else {
9012       SendToProgram("xboard\n", cps);
9013     }
9014 }
9015
9016
9017 void
9018 TwoMachinesEventIfReady P((void))
9019 {
9020   if (first.lastPing != first.lastPong) {
9021     DisplayMessage("", _("Waiting for first chess program"));
9022     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9023     return;
9024   }
9025   if (second.lastPing != second.lastPong) {
9026     DisplayMessage("", _("Waiting for second chess program"));
9027     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9028     return;
9029   }
9030   ThawUI();
9031   TwoMachinesEvent();
9032 }
9033
9034 void
9035 NextMatchGame P((void))
9036 {
9037     int index; /* [HGM] autoinc: step load index during match */
9038     Reset(FALSE, TRUE);
9039     if (*appData.loadGameFile != NULLCHAR) {
9040         index = appData.loadGameIndex;
9041         if(index < 0) { // [HGM] autoinc
9042             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9043             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9044         }
9045         LoadGameFromFile(appData.loadGameFile,
9046                          index,
9047                          appData.loadGameFile, FALSE);
9048     } else if (*appData.loadPositionFile != NULLCHAR) {
9049         index = appData.loadPositionIndex;
9050         if(index < 0) { // [HGM] autoinc
9051             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9052             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9053         }
9054         LoadPositionFromFile(appData.loadPositionFile,
9055                              index,
9056                              appData.loadPositionFile);
9057     }
9058     TwoMachinesEventIfReady();
9059 }
9060
9061 void UserAdjudicationEvent( int result )
9062 {
9063     ChessMove gameResult = GameIsDrawn;
9064
9065     if( result > 0 ) {
9066         gameResult = WhiteWins;
9067     }
9068     else if( result < 0 ) {
9069         gameResult = BlackWins;
9070     }
9071
9072     if( gameMode == TwoMachinesPlay ) {
9073         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9074     }
9075 }
9076
9077
9078 // [HGM] save: calculate checksum of game to make games easily identifiable
9079 int StringCheckSum(char *s)
9080 {
9081         int i = 0;
9082         if(s==NULL) return 0;
9083         while(*s) i = i*259 + *s++;
9084         return i;
9085 }
9086
9087 int GameCheckSum()
9088 {
9089         int i, sum=0;
9090         for(i=backwardMostMove; i<forwardMostMove; i++) {
9091                 sum += pvInfoList[i].depth;
9092                 sum += StringCheckSum(parseList[i]);
9093                 sum += StringCheckSum(commentList[i]);
9094                 sum *= 261;
9095         }
9096         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9097         return sum + StringCheckSum(commentList[i]);
9098 } // end of save patch
9099
9100 void
9101 GameEnds(result, resultDetails, whosays)
9102      ChessMove result;
9103      char *resultDetails;
9104      int whosays;
9105 {
9106     GameMode nextGameMode;
9107     int isIcsGame;
9108     char buf[MSG_SIZ], popupRequested = 0;
9109
9110     if(endingGame) return; /* [HGM] crash: forbid recursion */
9111     endingGame = 1;
9112     if(twoBoards) { // [HGM] dual: switch back to one board
9113         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9114         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9115     }
9116     if (appData.debugMode) {
9117       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9118               result, resultDetails ? resultDetails : "(null)", whosays);
9119     }
9120
9121     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9122
9123     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9124         /* If we are playing on ICS, the server decides when the
9125            game is over, but the engine can offer to draw, claim
9126            a draw, or resign.
9127          */
9128 #if ZIPPY
9129         if (appData.zippyPlay && first.initDone) {
9130             if (result == GameIsDrawn) {
9131                 /* In case draw still needs to be claimed */
9132                 SendToICS(ics_prefix);
9133                 SendToICS("draw\n");
9134             } else if (StrCaseStr(resultDetails, "resign")) {
9135                 SendToICS(ics_prefix);
9136                 SendToICS("resign\n");
9137             }
9138         }
9139 #endif
9140         endingGame = 0; /* [HGM] crash */
9141         return;
9142     }
9143
9144     /* If we're loading the game from a file, stop */
9145     if (whosays == GE_FILE) {
9146       (void) StopLoadGameTimer();
9147       gameFileFP = NULL;
9148     }
9149
9150     /* Cancel draw offers */
9151     first.offeredDraw = second.offeredDraw = 0;
9152
9153     /* If this is an ICS game, only ICS can really say it's done;
9154        if not, anyone can. */
9155     isIcsGame = (gameMode == IcsPlayingWhite ||
9156                  gameMode == IcsPlayingBlack ||
9157                  gameMode == IcsObserving    ||
9158                  gameMode == IcsExamining);
9159
9160     if (!isIcsGame || whosays == GE_ICS) {
9161         /* OK -- not an ICS game, or ICS said it was done */
9162         StopClocks();
9163         if (!isIcsGame && !appData.noChessProgram)
9164           SetUserThinkingEnables();
9165
9166         /* [HGM] if a machine claims the game end we verify this claim */
9167         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9168             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9169                 char claimer;
9170                 ChessMove trueResult = (ChessMove) -1;
9171
9172                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9173                                             first.twoMachinesColor[0] :
9174                                             second.twoMachinesColor[0] ;
9175
9176                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9177                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9178                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9179                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9180                 } else
9181                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9182                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9183                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9184                 } else
9185                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9186                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9187                 }
9188
9189                 // now verify win claims, but not in drop games, as we don't understand those yet
9190                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9191                                                  || gameInfo.variant == VariantGreat) &&
9192                     (result == WhiteWins && claimer == 'w' ||
9193                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9194                       if (appData.debugMode) {
9195                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9196                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9197                       }
9198                       if(result != trueResult) {
9199                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9200                               result = claimer == 'w' ? BlackWins : WhiteWins;
9201                               resultDetails = buf;
9202                       }
9203                 } else
9204                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9205                     && (forwardMostMove <= backwardMostMove ||
9206                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9207                         (claimer=='b')==(forwardMostMove&1))
9208                                                                                   ) {
9209                       /* [HGM] verify: draws that were not flagged are false claims */
9210                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9211                       result = claimer == 'w' ? BlackWins : WhiteWins;
9212                       resultDetails = buf;
9213                 }
9214                 /* (Claiming a loss is accepted no questions asked!) */
9215             }
9216             /* [HGM] bare: don't allow bare King to win */
9217             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9218                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9219                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9220                && result != GameIsDrawn)
9221             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9222                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9223                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9224                         if(p >= 0 && p <= (int)WhiteKing) k++;
9225                 }
9226                 if (appData.debugMode) {
9227                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9228                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9229                 }
9230                 if(k <= 1) {
9231                         result = GameIsDrawn;
9232                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9233                         resultDetails = buf;
9234                 }
9235             }
9236         }
9237
9238
9239         if(serverMoves != NULL && !loadFlag) { char c = '=';
9240             if(result==WhiteWins) c = '+';
9241             if(result==BlackWins) c = '-';
9242             if(resultDetails != NULL)
9243                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9244         }
9245         if (resultDetails != NULL) {
9246             gameInfo.result = result;
9247             gameInfo.resultDetails = StrSave(resultDetails);
9248
9249             /* display last move only if game was not loaded from file */
9250             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9251                 DisplayMove(currentMove - 1);
9252
9253             if (forwardMostMove != 0) {
9254                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9255                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9256                                                                 ) {
9257                     if (*appData.saveGameFile != NULLCHAR) {
9258                         SaveGameToFile(appData.saveGameFile, TRUE);
9259                     } else if (appData.autoSaveGames) {
9260                         AutoSaveGame();
9261                     }
9262                     if (*appData.savePositionFile != NULLCHAR) {
9263                         SavePositionToFile(appData.savePositionFile);
9264                     }
9265                 }
9266             }
9267
9268             /* Tell program how game ended in case it is learning */
9269             /* [HGM] Moved this to after saving the PGN, just in case */
9270             /* engine died and we got here through time loss. In that */
9271             /* case we will get a fatal error writing the pipe, which */
9272             /* would otherwise lose us the PGN.                       */
9273             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9274             /* output during GameEnds should never be fatal anymore   */
9275             if (gameMode == MachinePlaysWhite ||
9276                 gameMode == MachinePlaysBlack ||
9277                 gameMode == TwoMachinesPlay ||
9278                 gameMode == IcsPlayingWhite ||
9279                 gameMode == IcsPlayingBlack ||
9280                 gameMode == BeginningOfGame) {
9281                 char buf[MSG_SIZ];
9282                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9283                         resultDetails);
9284                 if (first.pr != NoProc) {
9285                     SendToProgram(buf, &first);
9286                 }
9287                 if (second.pr != NoProc &&
9288                     gameMode == TwoMachinesPlay) {
9289                     SendToProgram(buf, &second);
9290                 }
9291             }
9292         }
9293
9294         if (appData.icsActive) {
9295             if (appData.quietPlay &&
9296                 (gameMode == IcsPlayingWhite ||
9297                  gameMode == IcsPlayingBlack)) {
9298                 SendToICS(ics_prefix);
9299                 SendToICS("set shout 1\n");
9300             }
9301             nextGameMode = IcsIdle;
9302             ics_user_moved = FALSE;
9303             /* clean up premove.  It's ugly when the game has ended and the
9304              * premove highlights are still on the board.
9305              */
9306             if (gotPremove) {
9307               gotPremove = FALSE;
9308               ClearPremoveHighlights();
9309               DrawPosition(FALSE, boards[currentMove]);
9310             }
9311             if (whosays == GE_ICS) {
9312                 switch (result) {
9313                 case WhiteWins:
9314                     if (gameMode == IcsPlayingWhite)
9315                         PlayIcsWinSound();
9316                     else if(gameMode == IcsPlayingBlack)
9317                         PlayIcsLossSound();
9318                     break;
9319                 case BlackWins:
9320                     if (gameMode == IcsPlayingBlack)
9321                         PlayIcsWinSound();
9322                     else if(gameMode == IcsPlayingWhite)
9323                         PlayIcsLossSound();
9324                     break;
9325                 case GameIsDrawn:
9326                     PlayIcsDrawSound();
9327                     break;
9328                 default:
9329                     PlayIcsUnfinishedSound();
9330                 }
9331             }
9332         } else if (gameMode == EditGame ||
9333                    gameMode == PlayFromGameFile ||
9334                    gameMode == AnalyzeMode ||
9335                    gameMode == AnalyzeFile) {
9336             nextGameMode = gameMode;
9337         } else {
9338             nextGameMode = EndOfGame;
9339         }
9340         pausing = FALSE;
9341         ModeHighlight();
9342     } else {
9343         nextGameMode = gameMode;
9344     }
9345
9346     if (appData.noChessProgram) {
9347         gameMode = nextGameMode;
9348         ModeHighlight();
9349         endingGame = 0; /* [HGM] crash */
9350         return;
9351     }
9352
9353     if (first.reuse) {
9354         /* Put first chess program into idle state */
9355         if (first.pr != NoProc &&
9356             (gameMode == MachinePlaysWhite ||
9357              gameMode == MachinePlaysBlack ||
9358              gameMode == TwoMachinesPlay ||
9359              gameMode == IcsPlayingWhite ||
9360              gameMode == IcsPlayingBlack ||
9361              gameMode == BeginningOfGame)) {
9362             SendToProgram("force\n", &first);
9363             if (first.usePing) {
9364               char buf[MSG_SIZ];
9365               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9366               SendToProgram(buf, &first);
9367             }
9368         }
9369     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9370         /* Kill off first chess program */
9371         if (first.isr != NULL)
9372           RemoveInputSource(first.isr);
9373         first.isr = NULL;
9374
9375         if (first.pr != NoProc) {
9376             ExitAnalyzeMode();
9377             DoSleep( appData.delayBeforeQuit );
9378             SendToProgram("quit\n", &first);
9379             DoSleep( appData.delayAfterQuit );
9380             DestroyChildProcess(first.pr, first.useSigterm);
9381         }
9382         first.pr = NoProc;
9383     }
9384     if (second.reuse) {
9385         /* Put second chess program into idle state */
9386         if (second.pr != NoProc &&
9387             gameMode == TwoMachinesPlay) {
9388             SendToProgram("force\n", &second);
9389             if (second.usePing) {
9390               char buf[MSG_SIZ];
9391               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9392               SendToProgram(buf, &second);
9393             }
9394         }
9395     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9396         /* Kill off second chess program */
9397         if (second.isr != NULL)
9398           RemoveInputSource(second.isr);
9399         second.isr = NULL;
9400
9401         if (second.pr != NoProc) {
9402             DoSleep( appData.delayBeforeQuit );
9403             SendToProgram("quit\n", &second);
9404             DoSleep( appData.delayAfterQuit );
9405             DestroyChildProcess(second.pr, second.useSigterm);
9406         }
9407         second.pr = NoProc;
9408     }
9409
9410     if (matchMode && gameMode == TwoMachinesPlay) {
9411         switch (result) {
9412         case WhiteWins:
9413           if (first.twoMachinesColor[0] == 'w') {
9414             first.matchWins++;
9415           } else {
9416             second.matchWins++;
9417           }
9418           break;
9419         case BlackWins:
9420           if (first.twoMachinesColor[0] == 'b') {
9421             first.matchWins++;
9422           } else {
9423             second.matchWins++;
9424           }
9425           break;
9426         default:
9427           break;
9428         }
9429         if (matchGame < appData.matchGames) {
9430             char *tmp;
9431             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9432                 tmp = first.twoMachinesColor;
9433                 first.twoMachinesColor = second.twoMachinesColor;
9434                 second.twoMachinesColor = tmp;
9435             }
9436             gameMode = nextGameMode;
9437             matchGame++;
9438             if(appData.matchPause>10000 || appData.matchPause<10)
9439                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9440             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9441             endingGame = 0; /* [HGM] crash */
9442             return;
9443         } else {
9444             gameMode = nextGameMode;
9445             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9446                      first.tidy, second.tidy,
9447                      first.matchWins, second.matchWins,
9448                      appData.matchGames - (first.matchWins + second.matchWins));
9449             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9450             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9451                 first.twoMachinesColor = "black\n";
9452                 second.twoMachinesColor = "white\n";
9453             } else {
9454                 first.twoMachinesColor = "white\n";
9455                 second.twoMachinesColor = "black\n";
9456             }
9457         }
9458     }
9459     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9460         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9461       ExitAnalyzeMode();
9462     gameMode = nextGameMode;
9463     ModeHighlight();
9464     endingGame = 0;  /* [HGM] crash */
9465     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9466       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9467         matchMode = FALSE; appData.matchGames = matchGame = 0;
9468         DisplayNote(buf);
9469       }
9470     }
9471 }
9472
9473 /* Assumes program was just initialized (initString sent).
9474    Leaves program in force mode. */
9475 void
9476 FeedMovesToProgram(cps, upto)
9477      ChessProgramState *cps;
9478      int upto;
9479 {
9480     int i;
9481
9482     if (appData.debugMode)
9483       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9484               startedFromSetupPosition ? "position and " : "",
9485               backwardMostMove, upto, cps->which);
9486     if(currentlyInitializedVariant != gameInfo.variant) {
9487       char buf[MSG_SIZ];
9488         // [HGM] variantswitch: make engine aware of new variant
9489         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9490                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9491         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9492         SendToProgram(buf, cps);
9493         currentlyInitializedVariant = gameInfo.variant;
9494     }
9495     SendToProgram("force\n", cps);
9496     if (startedFromSetupPosition) {
9497         SendBoard(cps, backwardMostMove);
9498     if (appData.debugMode) {
9499         fprintf(debugFP, "feedMoves\n");
9500     }
9501     }
9502     for (i = backwardMostMove; i < upto; i++) {
9503         SendMoveToProgram(i, cps);
9504     }
9505 }
9506
9507
9508 void
9509 ResurrectChessProgram()
9510 {
9511      /* The chess program may have exited.
9512         If so, restart it and feed it all the moves made so far. */
9513
9514     if (appData.noChessProgram || first.pr != NoProc) return;
9515
9516     StartChessProgram(&first);
9517     InitChessProgram(&first, FALSE);
9518     FeedMovesToProgram(&first, currentMove);
9519
9520     if (!first.sendTime) {
9521         /* can't tell gnuchess what its clock should read,
9522            so we bow to its notion. */
9523         ResetClocks();
9524         timeRemaining[0][currentMove] = whiteTimeRemaining;
9525         timeRemaining[1][currentMove] = blackTimeRemaining;
9526     }
9527
9528     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9529                 appData.icsEngineAnalyze) && first.analysisSupport) {
9530       SendToProgram("analyze\n", &first);
9531       first.analyzing = TRUE;
9532     }
9533 }
9534
9535 /*
9536  * Button procedures
9537  */
9538 void
9539 Reset(redraw, init)
9540      int redraw, init;
9541 {
9542     int i;
9543
9544     if (appData.debugMode) {
9545         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9546                 redraw, init, gameMode);
9547     }
9548     CleanupTail(); // [HGM] vari: delete any stored variations
9549     pausing = pauseExamInvalid = FALSE;
9550     startedFromSetupPosition = blackPlaysFirst = FALSE;
9551     firstMove = TRUE;
9552     whiteFlag = blackFlag = FALSE;
9553     userOfferedDraw = FALSE;
9554     hintRequested = bookRequested = FALSE;
9555     first.maybeThinking = FALSE;
9556     second.maybeThinking = FALSE;
9557     first.bookSuspend = FALSE; // [HGM] book
9558     second.bookSuspend = FALSE;
9559     thinkOutput[0] = NULLCHAR;
9560     lastHint[0] = NULLCHAR;
9561     ClearGameInfo(&gameInfo);
9562     gameInfo.variant = StringToVariant(appData.variant);
9563     ics_user_moved = ics_clock_paused = FALSE;
9564     ics_getting_history = H_FALSE;
9565     ics_gamenum = -1;
9566     white_holding[0] = black_holding[0] = NULLCHAR;
9567     ClearProgramStats();
9568     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9569
9570     ResetFrontEnd();
9571     ClearHighlights();
9572     flipView = appData.flipView;
9573     ClearPremoveHighlights();
9574     gotPremove = FALSE;
9575     alarmSounded = FALSE;
9576
9577     GameEnds(EndOfFile, NULL, GE_PLAYER);
9578     if(appData.serverMovesName != NULL) {
9579         /* [HGM] prepare to make moves file for broadcasting */
9580         clock_t t = clock();
9581         if(serverMoves != NULL) fclose(serverMoves);
9582         serverMoves = fopen(appData.serverMovesName, "r");
9583         if(serverMoves != NULL) {
9584             fclose(serverMoves);
9585             /* delay 15 sec before overwriting, so all clients can see end */
9586             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9587         }
9588         serverMoves = fopen(appData.serverMovesName, "w");
9589     }
9590
9591     ExitAnalyzeMode();
9592     gameMode = BeginningOfGame;
9593     ModeHighlight();
9594     if(appData.icsActive) gameInfo.variant = VariantNormal;
9595     currentMove = forwardMostMove = backwardMostMove = 0;
9596     InitPosition(redraw);
9597     for (i = 0; i < MAX_MOVES; i++) {
9598         if (commentList[i] != NULL) {
9599             free(commentList[i]);
9600             commentList[i] = NULL;
9601         }
9602     }
9603     ResetClocks();
9604     timeRemaining[0][0] = whiteTimeRemaining;
9605     timeRemaining[1][0] = blackTimeRemaining;
9606     if (first.pr == NULL) {
9607         StartChessProgram(&first);
9608     }
9609     if (init) {
9610             InitChessProgram(&first, startedFromSetupPosition);
9611     }
9612     DisplayTitle("");
9613     DisplayMessage("", "");
9614     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9615     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9616 }
9617
9618 void
9619 AutoPlayGameLoop()
9620 {
9621     for (;;) {
9622         if (!AutoPlayOneMove())
9623           return;
9624         if (matchMode || appData.timeDelay == 0)
9625           continue;
9626         if (appData.timeDelay < 0)
9627           return;
9628         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9629         break;
9630     }
9631 }
9632
9633
9634 int
9635 AutoPlayOneMove()
9636 {
9637     int fromX, fromY, toX, toY;
9638
9639     if (appData.debugMode) {
9640       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9641     }
9642
9643     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9644       return FALSE;
9645
9646     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9647       pvInfoList[currentMove].depth = programStats.depth;
9648       pvInfoList[currentMove].score = programStats.score;
9649       pvInfoList[currentMove].time  = 0;
9650       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9651     }
9652
9653     if (currentMove >= forwardMostMove) {
9654       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9655       gameMode = EditGame;
9656       ModeHighlight();
9657
9658       /* [AS] Clear current move marker at the end of a game */
9659       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9660
9661       return FALSE;
9662     }
9663
9664     toX = moveList[currentMove][2] - AAA;
9665     toY = moveList[currentMove][3] - ONE;
9666
9667     if (moveList[currentMove][1] == '@') {
9668         if (appData.highlightLastMove) {
9669             SetHighlights(-1, -1, toX, toY);
9670         }
9671     } else {
9672         fromX = moveList[currentMove][0] - AAA;
9673         fromY = moveList[currentMove][1] - ONE;
9674
9675         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9676
9677         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9678
9679         if (appData.highlightLastMove) {
9680             SetHighlights(fromX, fromY, toX, toY);
9681         }
9682     }
9683     DisplayMove(currentMove);
9684     SendMoveToProgram(currentMove++, &first);
9685     DisplayBothClocks();
9686     DrawPosition(FALSE, boards[currentMove]);
9687     // [HGM] PV info: always display, routine tests if empty
9688     DisplayComment(currentMove - 1, commentList[currentMove]);
9689     return TRUE;
9690 }
9691
9692
9693 int
9694 LoadGameOneMove(readAhead)
9695      ChessMove readAhead;
9696 {
9697     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9698     char promoChar = NULLCHAR;
9699     ChessMove moveType;
9700     char move[MSG_SIZ];
9701     char *p, *q;
9702
9703     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9704         gameMode != AnalyzeMode && gameMode != Training) {
9705         gameFileFP = NULL;
9706         return FALSE;
9707     }
9708
9709     yyboardindex = forwardMostMove;
9710     if (readAhead != EndOfFile) {
9711       moveType = readAhead;
9712     } else {
9713       if (gameFileFP == NULL)
9714           return FALSE;
9715       moveType = (ChessMove) Myylex();
9716     }
9717
9718     done = FALSE;
9719     switch (moveType) {
9720       case Comment:
9721         if (appData.debugMode)
9722           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9723         p = yy_text;
9724
9725         /* append the comment but don't display it */
9726         AppendComment(currentMove, p, FALSE);
9727         return TRUE;
9728
9729       case WhiteCapturesEnPassant:
9730       case BlackCapturesEnPassant:
9731       case WhitePromotion:
9732       case BlackPromotion:
9733       case WhiteNonPromotion:
9734       case BlackNonPromotion:
9735       case NormalMove:
9736       case WhiteKingSideCastle:
9737       case WhiteQueenSideCastle:
9738       case BlackKingSideCastle:
9739       case BlackQueenSideCastle:
9740       case WhiteKingSideCastleWild:
9741       case WhiteQueenSideCastleWild:
9742       case BlackKingSideCastleWild:
9743       case BlackQueenSideCastleWild:
9744       /* PUSH Fabien */
9745       case WhiteHSideCastleFR:
9746       case WhiteASideCastleFR:
9747       case BlackHSideCastleFR:
9748       case BlackASideCastleFR:
9749       /* POP Fabien */
9750         if (appData.debugMode)
9751           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9752         fromX = currentMoveString[0] - AAA;
9753         fromY = currentMoveString[1] - ONE;
9754         toX = currentMoveString[2] - AAA;
9755         toY = currentMoveString[3] - ONE;
9756         promoChar = currentMoveString[4];
9757         break;
9758
9759       case WhiteDrop:
9760       case BlackDrop:
9761         if (appData.debugMode)
9762           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9763         fromX = moveType == WhiteDrop ?
9764           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9765         (int) CharToPiece(ToLower(currentMoveString[0]));
9766         fromY = DROP_RANK;
9767         toX = currentMoveString[2] - AAA;
9768         toY = currentMoveString[3] - ONE;
9769         break;
9770
9771       case WhiteWins:
9772       case BlackWins:
9773       case GameIsDrawn:
9774       case GameUnfinished:
9775         if (appData.debugMode)
9776           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9777         p = strchr(yy_text, '{');
9778         if (p == NULL) p = strchr(yy_text, '(');
9779         if (p == NULL) {
9780             p = yy_text;
9781             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9782         } else {
9783             q = strchr(p, *p == '{' ? '}' : ')');
9784             if (q != NULL) *q = NULLCHAR;
9785             p++;
9786         }
9787         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9788         GameEnds(moveType, p, GE_FILE);
9789         done = TRUE;
9790         if (cmailMsgLoaded) {
9791             ClearHighlights();
9792             flipView = WhiteOnMove(currentMove);
9793             if (moveType == GameUnfinished) flipView = !flipView;
9794             if (appData.debugMode)
9795               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9796         }
9797         break;
9798
9799       case EndOfFile:
9800         if (appData.debugMode)
9801           fprintf(debugFP, "Parser hit end of file\n");
9802         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9803           case MT_NONE:
9804           case MT_CHECK:
9805             break;
9806           case MT_CHECKMATE:
9807           case MT_STAINMATE:
9808             if (WhiteOnMove(currentMove)) {
9809                 GameEnds(BlackWins, "Black mates", GE_FILE);
9810             } else {
9811                 GameEnds(WhiteWins, "White mates", GE_FILE);
9812             }
9813             break;
9814           case MT_STALEMATE:
9815             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9816             break;
9817         }
9818         done = TRUE;
9819         break;
9820
9821       case MoveNumberOne:
9822         if (lastLoadGameStart == GNUChessGame) {
9823             /* GNUChessGames have numbers, but they aren't move numbers */
9824             if (appData.debugMode)
9825               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9826                       yy_text, (int) moveType);
9827             return LoadGameOneMove(EndOfFile); /* tail recursion */
9828         }
9829         /* else fall thru */
9830
9831       case XBoardGame:
9832       case GNUChessGame:
9833       case PGNTag:
9834         /* Reached start of next game in file */
9835         if (appData.debugMode)
9836           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9837         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9838           case MT_NONE:
9839           case MT_CHECK:
9840             break;
9841           case MT_CHECKMATE:
9842           case MT_STAINMATE:
9843             if (WhiteOnMove(currentMove)) {
9844                 GameEnds(BlackWins, "Black mates", GE_FILE);
9845             } else {
9846                 GameEnds(WhiteWins, "White mates", GE_FILE);
9847             }
9848             break;
9849           case MT_STALEMATE:
9850             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9851             break;
9852         }
9853         done = TRUE;
9854         break;
9855
9856       case PositionDiagram:     /* should not happen; ignore */
9857       case ElapsedTime:         /* ignore */
9858       case NAG:                 /* ignore */
9859         if (appData.debugMode)
9860           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9861                   yy_text, (int) moveType);
9862         return LoadGameOneMove(EndOfFile); /* tail recursion */
9863
9864       case IllegalMove:
9865         if (appData.testLegality) {
9866             if (appData.debugMode)
9867               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9868             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9869                     (forwardMostMove / 2) + 1,
9870                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9871             DisplayError(move, 0);
9872             done = TRUE;
9873         } else {
9874             if (appData.debugMode)
9875               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9876                       yy_text, currentMoveString);
9877             fromX = currentMoveString[0] - AAA;
9878             fromY = currentMoveString[1] - ONE;
9879             toX = currentMoveString[2] - AAA;
9880             toY = currentMoveString[3] - ONE;
9881             promoChar = currentMoveString[4];
9882         }
9883         break;
9884
9885       case AmbiguousMove:
9886         if (appData.debugMode)
9887           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9888         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9889                 (forwardMostMove / 2) + 1,
9890                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9891         DisplayError(move, 0);
9892         done = TRUE;
9893         break;
9894
9895       default:
9896       case ImpossibleMove:
9897         if (appData.debugMode)
9898           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9899         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9900                 (forwardMostMove / 2) + 1,
9901                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9902         DisplayError(move, 0);
9903         done = TRUE;
9904         break;
9905     }
9906
9907     if (done) {
9908         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9909             DrawPosition(FALSE, boards[currentMove]);
9910             DisplayBothClocks();
9911             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9912               DisplayComment(currentMove - 1, commentList[currentMove]);
9913         }
9914         (void) StopLoadGameTimer();
9915         gameFileFP = NULL;
9916         cmailOldMove = forwardMostMove;
9917         return FALSE;
9918     } else {
9919         /* currentMoveString is set as a side-effect of yylex */
9920
9921         thinkOutput[0] = NULLCHAR;
9922         MakeMove(fromX, fromY, toX, toY, promoChar);
9923         currentMove = forwardMostMove;
9924         return TRUE;
9925     }
9926 }
9927
9928 /* Load the nth game from the given file */
9929 int
9930 LoadGameFromFile(filename, n, title, useList)
9931      char *filename;
9932      int n;
9933      char *title;
9934      /*Boolean*/ int useList;
9935 {
9936     FILE *f;
9937     char buf[MSG_SIZ];
9938
9939     if (strcmp(filename, "-") == 0) {
9940         f = stdin;
9941         title = "stdin";
9942     } else {
9943         f = fopen(filename, "rb");
9944         if (f == NULL) {
9945           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9946             DisplayError(buf, errno);
9947             return FALSE;
9948         }
9949     }
9950     if (fseek(f, 0, 0) == -1) {
9951         /* f is not seekable; probably a pipe */
9952         useList = FALSE;
9953     }
9954     if (useList && n == 0) {
9955         int error = GameListBuild(f);
9956         if (error) {
9957             DisplayError(_("Cannot build game list"), error);
9958         } else if (!ListEmpty(&gameList) &&
9959                    ((ListGame *) gameList.tailPred)->number > 1) {
9960             GameListPopUp(f, title);
9961             return TRUE;
9962         }
9963         GameListDestroy();
9964         n = 1;
9965     }
9966     if (n == 0) n = 1;
9967     return LoadGame(f, n, title, FALSE);
9968 }
9969
9970
9971 void
9972 MakeRegisteredMove()
9973 {
9974     int fromX, fromY, toX, toY;
9975     char promoChar;
9976     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9977         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9978           case CMAIL_MOVE:
9979           case CMAIL_DRAW:
9980             if (appData.debugMode)
9981               fprintf(debugFP, "Restoring %s for game %d\n",
9982                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9983
9984             thinkOutput[0] = NULLCHAR;
9985             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9986             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9987             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9988             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9989             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9990             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9991             MakeMove(fromX, fromY, toX, toY, promoChar);
9992             ShowMove(fromX, fromY, toX, toY);
9993
9994             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9995               case MT_NONE:
9996               case MT_CHECK:
9997                 break;
9998
9999               case MT_CHECKMATE:
10000               case MT_STAINMATE:
10001                 if (WhiteOnMove(currentMove)) {
10002                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10003                 } else {
10004                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10005                 }
10006                 break;
10007
10008               case MT_STALEMATE:
10009                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10010                 break;
10011             }
10012
10013             break;
10014
10015           case CMAIL_RESIGN:
10016             if (WhiteOnMove(currentMove)) {
10017                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10018             } else {
10019                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10020             }
10021             break;
10022
10023           case CMAIL_ACCEPT:
10024             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10025             break;
10026
10027           default:
10028             break;
10029         }
10030     }
10031
10032     return;
10033 }
10034
10035 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10036 int
10037 CmailLoadGame(f, gameNumber, title, useList)
10038      FILE *f;
10039      int gameNumber;
10040      char *title;
10041      int useList;
10042 {
10043     int retVal;
10044
10045     if (gameNumber > nCmailGames) {
10046         DisplayError(_("No more games in this message"), 0);
10047         return FALSE;
10048     }
10049     if (f == lastLoadGameFP) {
10050         int offset = gameNumber - lastLoadGameNumber;
10051         if (offset == 0) {
10052             cmailMsg[0] = NULLCHAR;
10053             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10054                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10055                 nCmailMovesRegistered--;
10056             }
10057             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10058             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10059                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10060             }
10061         } else {
10062             if (! RegisterMove()) return FALSE;
10063         }
10064     }
10065
10066     retVal = LoadGame(f, gameNumber, title, useList);
10067
10068     /* Make move registered during previous look at this game, if any */
10069     MakeRegisteredMove();
10070
10071     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10072         commentList[currentMove]
10073           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10074         DisplayComment(currentMove - 1, commentList[currentMove]);
10075     }
10076
10077     return retVal;
10078 }
10079
10080 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10081 int
10082 ReloadGame(offset)
10083      int offset;
10084 {
10085     int gameNumber = lastLoadGameNumber + offset;
10086     if (lastLoadGameFP == NULL) {
10087         DisplayError(_("No game has been loaded yet"), 0);
10088         return FALSE;
10089     }
10090     if (gameNumber <= 0) {
10091         DisplayError(_("Can't back up any further"), 0);
10092         return FALSE;
10093     }
10094     if (cmailMsgLoaded) {
10095         return CmailLoadGame(lastLoadGameFP, gameNumber,
10096                              lastLoadGameTitle, lastLoadGameUseList);
10097     } else {
10098         return LoadGame(lastLoadGameFP, gameNumber,
10099                         lastLoadGameTitle, lastLoadGameUseList);
10100     }
10101 }
10102
10103
10104
10105 /* Load the nth game from open file f */
10106 int
10107 LoadGame(f, gameNumber, title, useList)
10108      FILE *f;
10109      int gameNumber;
10110      char *title;
10111      int useList;
10112 {
10113     ChessMove cm;
10114     char buf[MSG_SIZ];
10115     int gn = gameNumber;
10116     ListGame *lg = NULL;
10117     int numPGNTags = 0;
10118     int err;
10119     GameMode oldGameMode;
10120     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10121
10122     if (appData.debugMode)
10123         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10124
10125     if (gameMode == Training )
10126         SetTrainingModeOff();
10127
10128     oldGameMode = gameMode;
10129     if (gameMode != BeginningOfGame) {
10130       Reset(FALSE, TRUE);
10131     }
10132
10133     gameFileFP = f;
10134     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10135         fclose(lastLoadGameFP);
10136     }
10137
10138     if (useList) {
10139         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10140
10141         if (lg) {
10142             fseek(f, lg->offset, 0);
10143             GameListHighlight(gameNumber);
10144             gn = 1;
10145         }
10146         else {
10147             DisplayError(_("Game number out of range"), 0);
10148             return FALSE;
10149         }
10150     } else {
10151         GameListDestroy();
10152         if (fseek(f, 0, 0) == -1) {
10153             if (f == lastLoadGameFP ?
10154                 gameNumber == lastLoadGameNumber + 1 :
10155                 gameNumber == 1) {
10156                 gn = 1;
10157             } else {
10158                 DisplayError(_("Can't seek on game file"), 0);
10159                 return FALSE;
10160             }
10161         }
10162     }
10163     lastLoadGameFP = f;
10164     lastLoadGameNumber = gameNumber;
10165     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10166     lastLoadGameUseList = useList;
10167
10168     yynewfile(f);
10169
10170     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10171       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10172                 lg->gameInfo.black);
10173             DisplayTitle(buf);
10174     } else if (*title != NULLCHAR) {
10175         if (gameNumber > 1) {
10176           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10177             DisplayTitle(buf);
10178         } else {
10179             DisplayTitle(title);
10180         }
10181     }
10182
10183     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10184         gameMode = PlayFromGameFile;
10185         ModeHighlight();
10186     }
10187
10188     currentMove = forwardMostMove = backwardMostMove = 0;
10189     CopyBoard(boards[0], initialPosition);
10190     StopClocks();
10191
10192     /*
10193      * Skip the first gn-1 games in the file.
10194      * Also skip over anything that precedes an identifiable
10195      * start of game marker, to avoid being confused by
10196      * garbage at the start of the file.  Currently
10197      * recognized start of game markers are the move number "1",
10198      * the pattern "gnuchess .* game", the pattern
10199      * "^[#;%] [^ ]* game file", and a PGN tag block.
10200      * A game that starts with one of the latter two patterns
10201      * will also have a move number 1, possibly
10202      * following a position diagram.
10203      * 5-4-02: Let's try being more lenient and allowing a game to
10204      * start with an unnumbered move.  Does that break anything?
10205      */
10206     cm = lastLoadGameStart = EndOfFile;
10207     while (gn > 0) {
10208         yyboardindex = forwardMostMove;
10209         cm = (ChessMove) Myylex();
10210         switch (cm) {
10211           case EndOfFile:
10212             if (cmailMsgLoaded) {
10213                 nCmailGames = CMAIL_MAX_GAMES - gn;
10214             } else {
10215                 Reset(TRUE, TRUE);
10216                 DisplayError(_("Game not found in file"), 0);
10217             }
10218             return FALSE;
10219
10220           case GNUChessGame:
10221           case XBoardGame:
10222             gn--;
10223             lastLoadGameStart = cm;
10224             break;
10225
10226           case MoveNumberOne:
10227             switch (lastLoadGameStart) {
10228               case GNUChessGame:
10229               case XBoardGame:
10230               case PGNTag:
10231                 break;
10232               case MoveNumberOne:
10233               case EndOfFile:
10234                 gn--;           /* count this game */
10235                 lastLoadGameStart = cm;
10236                 break;
10237               default:
10238                 /* impossible */
10239                 break;
10240             }
10241             break;
10242
10243           case PGNTag:
10244             switch (lastLoadGameStart) {
10245               case GNUChessGame:
10246               case PGNTag:
10247               case MoveNumberOne:
10248               case EndOfFile:
10249                 gn--;           /* count this game */
10250                 lastLoadGameStart = cm;
10251                 break;
10252               case XBoardGame:
10253                 lastLoadGameStart = cm; /* game counted already */
10254                 break;
10255               default:
10256                 /* impossible */
10257                 break;
10258             }
10259             if (gn > 0) {
10260                 do {
10261                     yyboardindex = forwardMostMove;
10262                     cm = (ChessMove) Myylex();
10263                 } while (cm == PGNTag || cm == Comment);
10264             }
10265             break;
10266
10267           case WhiteWins:
10268           case BlackWins:
10269           case GameIsDrawn:
10270             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10271                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10272                     != CMAIL_OLD_RESULT) {
10273                     nCmailResults ++ ;
10274                     cmailResult[  CMAIL_MAX_GAMES
10275                                 - gn - 1] = CMAIL_OLD_RESULT;
10276                 }
10277             }
10278             break;
10279
10280           case NormalMove:
10281             /* Only a NormalMove can be at the start of a game
10282              * without a position diagram. */
10283             if (lastLoadGameStart == EndOfFile ) {
10284               gn--;
10285               lastLoadGameStart = MoveNumberOne;
10286             }
10287             break;
10288
10289           default:
10290             break;
10291         }
10292     }
10293
10294     if (appData.debugMode)
10295       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10296
10297     if (cm == XBoardGame) {
10298         /* Skip any header junk before position diagram and/or move 1 */
10299         for (;;) {
10300             yyboardindex = forwardMostMove;
10301             cm = (ChessMove) Myylex();
10302
10303             if (cm == EndOfFile ||
10304                 cm == GNUChessGame || cm == XBoardGame) {
10305                 /* Empty game; pretend end-of-file and handle later */
10306                 cm = EndOfFile;
10307                 break;
10308             }
10309
10310             if (cm == MoveNumberOne || cm == PositionDiagram ||
10311                 cm == PGNTag || cm == Comment)
10312               break;
10313         }
10314     } else if (cm == GNUChessGame) {
10315         if (gameInfo.event != NULL) {
10316             free(gameInfo.event);
10317         }
10318         gameInfo.event = StrSave(yy_text);
10319     }
10320
10321     startedFromSetupPosition = FALSE;
10322     while (cm == PGNTag) {
10323         if (appData.debugMode)
10324           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10325         err = ParsePGNTag(yy_text, &gameInfo);
10326         if (!err) numPGNTags++;
10327
10328         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10329         if(gameInfo.variant != oldVariant) {
10330             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10331             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10332             InitPosition(TRUE);
10333             oldVariant = gameInfo.variant;
10334             if (appData.debugMode)
10335               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10336         }
10337
10338
10339         if (gameInfo.fen != NULL) {
10340           Board initial_position;
10341           startedFromSetupPosition = TRUE;
10342           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10343             Reset(TRUE, TRUE);
10344             DisplayError(_("Bad FEN position in file"), 0);
10345             return FALSE;
10346           }
10347           CopyBoard(boards[0], initial_position);
10348           if (blackPlaysFirst) {
10349             currentMove = forwardMostMove = backwardMostMove = 1;
10350             CopyBoard(boards[1], initial_position);
10351             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10352             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10353             timeRemaining[0][1] = whiteTimeRemaining;
10354             timeRemaining[1][1] = blackTimeRemaining;
10355             if (commentList[0] != NULL) {
10356               commentList[1] = commentList[0];
10357               commentList[0] = NULL;
10358             }
10359           } else {
10360             currentMove = forwardMostMove = backwardMostMove = 0;
10361           }
10362           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10363           {   int i;
10364               initialRulePlies = FENrulePlies;
10365               for( i=0; i< nrCastlingRights; i++ )
10366                   initialRights[i] = initial_position[CASTLING][i];
10367           }
10368           yyboardindex = forwardMostMove;
10369           free(gameInfo.fen);
10370           gameInfo.fen = NULL;
10371         }
10372
10373         yyboardindex = forwardMostMove;
10374         cm = (ChessMove) Myylex();
10375
10376         /* Handle comments interspersed among the tags */
10377         while (cm == Comment) {
10378             char *p;
10379             if (appData.debugMode)
10380               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10381             p = yy_text;
10382             AppendComment(currentMove, p, FALSE);
10383             yyboardindex = forwardMostMove;
10384             cm = (ChessMove) Myylex();
10385         }
10386     }
10387
10388     /* don't rely on existence of Event tag since if game was
10389      * pasted from clipboard the Event tag may not exist
10390      */
10391     if (numPGNTags > 0){
10392         char *tags;
10393         if (gameInfo.variant == VariantNormal) {
10394           VariantClass v = StringToVariant(gameInfo.event);
10395           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10396           if(v < VariantShogi) gameInfo.variant = v;
10397         }
10398         if (!matchMode) {
10399           if( appData.autoDisplayTags ) {
10400             tags = PGNTags(&gameInfo);
10401             TagsPopUp(tags, CmailMsg());
10402             free(tags);
10403           }
10404         }
10405     } else {
10406         /* Make something up, but don't display it now */
10407         SetGameInfo();
10408         TagsPopDown();
10409     }
10410
10411     if (cm == PositionDiagram) {
10412         int i, j;
10413         char *p;
10414         Board initial_position;
10415
10416         if (appData.debugMode)
10417           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10418
10419         if (!startedFromSetupPosition) {
10420             p = yy_text;
10421             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10422               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10423                 switch (*p) {
10424                   case '[':
10425                   case '-':
10426                   case ' ':
10427                   case '\t':
10428                   case '\n':
10429                   case '\r':
10430                     break;
10431                   default:
10432                     initial_position[i][j++] = CharToPiece(*p);
10433                     break;
10434                 }
10435             while (*p == ' ' || *p == '\t' ||
10436                    *p == '\n' || *p == '\r') p++;
10437
10438             if (strncmp(p, "black", strlen("black"))==0)
10439               blackPlaysFirst = TRUE;
10440             else
10441               blackPlaysFirst = FALSE;
10442             startedFromSetupPosition = TRUE;
10443
10444             CopyBoard(boards[0], initial_position);
10445             if (blackPlaysFirst) {
10446                 currentMove = forwardMostMove = backwardMostMove = 1;
10447                 CopyBoard(boards[1], initial_position);
10448                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10449                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10450                 timeRemaining[0][1] = whiteTimeRemaining;
10451                 timeRemaining[1][1] = blackTimeRemaining;
10452                 if (commentList[0] != NULL) {
10453                     commentList[1] = commentList[0];
10454                     commentList[0] = NULL;
10455                 }
10456             } else {
10457                 currentMove = forwardMostMove = backwardMostMove = 0;
10458             }
10459         }
10460         yyboardindex = forwardMostMove;
10461         cm = (ChessMove) Myylex();
10462     }
10463
10464     if (first.pr == NoProc) {
10465         StartChessProgram(&first);
10466     }
10467     InitChessProgram(&first, FALSE);
10468     SendToProgram("force\n", &first);
10469     if (startedFromSetupPosition) {
10470         SendBoard(&first, forwardMostMove);
10471     if (appData.debugMode) {
10472         fprintf(debugFP, "Load Game\n");
10473     }
10474         DisplayBothClocks();
10475     }
10476
10477     /* [HGM] server: flag to write setup moves in broadcast file as one */
10478     loadFlag = appData.suppressLoadMoves;
10479
10480     while (cm == Comment) {
10481         char *p;
10482         if (appData.debugMode)
10483           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10484         p = yy_text;
10485         AppendComment(currentMove, p, FALSE);
10486         yyboardindex = forwardMostMove;
10487         cm = (ChessMove) Myylex();
10488     }
10489
10490     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10491         cm == WhiteWins || cm == BlackWins ||
10492         cm == GameIsDrawn || cm == GameUnfinished) {
10493         DisplayMessage("", _("No moves in game"));
10494         if (cmailMsgLoaded) {
10495             if (appData.debugMode)
10496               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10497             ClearHighlights();
10498             flipView = FALSE;
10499         }
10500         DrawPosition(FALSE, boards[currentMove]);
10501         DisplayBothClocks();
10502         gameMode = EditGame;
10503         ModeHighlight();
10504         gameFileFP = NULL;
10505         cmailOldMove = 0;
10506         return TRUE;
10507     }
10508
10509     // [HGM] PV info: routine tests if comment empty
10510     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10511         DisplayComment(currentMove - 1, commentList[currentMove]);
10512     }
10513     if (!matchMode && appData.timeDelay != 0)
10514       DrawPosition(FALSE, boards[currentMove]);
10515
10516     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10517       programStats.ok_to_send = 1;
10518     }
10519
10520     /* if the first token after the PGN tags is a move
10521      * and not move number 1, retrieve it from the parser
10522      */
10523     if (cm != MoveNumberOne)
10524         LoadGameOneMove(cm);
10525
10526     /* load the remaining moves from the file */
10527     while (LoadGameOneMove(EndOfFile)) {
10528       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10529       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10530     }
10531
10532     /* rewind to the start of the game */
10533     currentMove = backwardMostMove;
10534
10535     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10536
10537     if (oldGameMode == AnalyzeFile ||
10538         oldGameMode == AnalyzeMode) {
10539       AnalyzeFileEvent();
10540     }
10541
10542     if (matchMode || appData.timeDelay == 0) {
10543       ToEndEvent();
10544       gameMode = EditGame;
10545       ModeHighlight();
10546     } else if (appData.timeDelay > 0) {
10547       AutoPlayGameLoop();
10548     }
10549
10550     if (appData.debugMode)
10551         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10552
10553     loadFlag = 0; /* [HGM] true game starts */
10554     return TRUE;
10555 }
10556
10557 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10558 int
10559 ReloadPosition(offset)
10560      int offset;
10561 {
10562     int positionNumber = lastLoadPositionNumber + offset;
10563     if (lastLoadPositionFP == NULL) {
10564         DisplayError(_("No position has been loaded yet"), 0);
10565         return FALSE;
10566     }
10567     if (positionNumber <= 0) {
10568         DisplayError(_("Can't back up any further"), 0);
10569         return FALSE;
10570     }
10571     return LoadPosition(lastLoadPositionFP, positionNumber,
10572                         lastLoadPositionTitle);
10573 }
10574
10575 /* Load the nth position from the given file */
10576 int
10577 LoadPositionFromFile(filename, n, title)
10578      char *filename;
10579      int n;
10580      char *title;
10581 {
10582     FILE *f;
10583     char buf[MSG_SIZ];
10584
10585     if (strcmp(filename, "-") == 0) {
10586         return LoadPosition(stdin, n, "stdin");
10587     } else {
10588         f = fopen(filename, "rb");
10589         if (f == NULL) {
10590             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10591             DisplayError(buf, errno);
10592             return FALSE;
10593         } else {
10594             return LoadPosition(f, n, title);
10595         }
10596     }
10597 }
10598
10599 /* Load the nth position from the given open file, and close it */
10600 int
10601 LoadPosition(f, positionNumber, title)
10602      FILE *f;
10603      int positionNumber;
10604      char *title;
10605 {
10606     char *p, line[MSG_SIZ];
10607     Board initial_position;
10608     int i, j, fenMode, pn;
10609
10610     if (gameMode == Training )
10611         SetTrainingModeOff();
10612
10613     if (gameMode != BeginningOfGame) {
10614         Reset(FALSE, TRUE);
10615     }
10616     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10617         fclose(lastLoadPositionFP);
10618     }
10619     if (positionNumber == 0) positionNumber = 1;
10620     lastLoadPositionFP = f;
10621     lastLoadPositionNumber = positionNumber;
10622     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10623     if (first.pr == NoProc) {
10624       StartChessProgram(&first);
10625       InitChessProgram(&first, FALSE);
10626     }
10627     pn = positionNumber;
10628     if (positionNumber < 0) {
10629         /* Negative position number means to seek to that byte offset */
10630         if (fseek(f, -positionNumber, 0) == -1) {
10631             DisplayError(_("Can't seek on position file"), 0);
10632             return FALSE;
10633         };
10634         pn = 1;
10635     } else {
10636         if (fseek(f, 0, 0) == -1) {
10637             if (f == lastLoadPositionFP ?
10638                 positionNumber == lastLoadPositionNumber + 1 :
10639                 positionNumber == 1) {
10640                 pn = 1;
10641             } else {
10642                 DisplayError(_("Can't seek on position file"), 0);
10643                 return FALSE;
10644             }
10645         }
10646     }
10647     /* See if this file is FEN or old-style xboard */
10648     if (fgets(line, MSG_SIZ, f) == NULL) {
10649         DisplayError(_("Position not found in file"), 0);
10650         return FALSE;
10651     }
10652     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10653     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10654
10655     if (pn >= 2) {
10656         if (fenMode || line[0] == '#') pn--;
10657         while (pn > 0) {
10658             /* skip positions before number pn */
10659             if (fgets(line, MSG_SIZ, f) == NULL) {
10660                 Reset(TRUE, TRUE);
10661                 DisplayError(_("Position not found in file"), 0);
10662                 return FALSE;
10663             }
10664             if (fenMode || line[0] == '#') pn--;
10665         }
10666     }
10667
10668     if (fenMode) {
10669         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10670             DisplayError(_("Bad FEN position in file"), 0);
10671             return FALSE;
10672         }
10673     } else {
10674         (void) fgets(line, MSG_SIZ, f);
10675         (void) fgets(line, MSG_SIZ, f);
10676
10677         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10678             (void) fgets(line, MSG_SIZ, f);
10679             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10680                 if (*p == ' ')
10681                   continue;
10682                 initial_position[i][j++] = CharToPiece(*p);
10683             }
10684         }
10685
10686         blackPlaysFirst = FALSE;
10687         if (!feof(f)) {
10688             (void) fgets(line, MSG_SIZ, f);
10689             if (strncmp(line, "black", strlen("black"))==0)
10690               blackPlaysFirst = TRUE;
10691         }
10692     }
10693     startedFromSetupPosition = TRUE;
10694
10695     SendToProgram("force\n", &first);
10696     CopyBoard(boards[0], initial_position);
10697     if (blackPlaysFirst) {
10698         currentMove = forwardMostMove = backwardMostMove = 1;
10699         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10700         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10701         CopyBoard(boards[1], initial_position);
10702         DisplayMessage("", _("Black to play"));
10703     } else {
10704         currentMove = forwardMostMove = backwardMostMove = 0;
10705         DisplayMessage("", _("White to play"));
10706     }
10707     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10708     SendBoard(&first, forwardMostMove);
10709     if (appData.debugMode) {
10710 int i, j;
10711   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10712   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10713         fprintf(debugFP, "Load Position\n");
10714     }
10715
10716     if (positionNumber > 1) {
10717       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10718         DisplayTitle(line);
10719     } else {
10720         DisplayTitle(title);
10721     }
10722     gameMode = EditGame;
10723     ModeHighlight();
10724     ResetClocks();
10725     timeRemaining[0][1] = whiteTimeRemaining;
10726     timeRemaining[1][1] = blackTimeRemaining;
10727     DrawPosition(FALSE, boards[currentMove]);
10728
10729     return TRUE;
10730 }
10731
10732
10733 void
10734 CopyPlayerNameIntoFileName(dest, src)
10735      char **dest, *src;
10736 {
10737     while (*src != NULLCHAR && *src != ',') {
10738         if (*src == ' ') {
10739             *(*dest)++ = '_';
10740             src++;
10741         } else {
10742             *(*dest)++ = *src++;
10743         }
10744     }
10745 }
10746
10747 char *DefaultFileName(ext)
10748      char *ext;
10749 {
10750     static char def[MSG_SIZ];
10751     char *p;
10752
10753     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10754         p = def;
10755         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10756         *p++ = '-';
10757         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10758         *p++ = '.';
10759         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10760     } else {
10761         def[0] = NULLCHAR;
10762     }
10763     return def;
10764 }
10765
10766 /* Save the current game to the given file */
10767 int
10768 SaveGameToFile(filename, append)
10769      char *filename;
10770      int append;
10771 {
10772     FILE *f;
10773     char buf[MSG_SIZ];
10774
10775     if (strcmp(filename, "-") == 0) {
10776         return SaveGame(stdout, 0, NULL);
10777     } else {
10778         f = fopen(filename, append ? "a" : "w");
10779         if (f == NULL) {
10780             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10781             DisplayError(buf, errno);
10782             return FALSE;
10783         } else {
10784             return SaveGame(f, 0, NULL);
10785         }
10786     }
10787 }
10788
10789 char *
10790 SavePart(str)
10791      char *str;
10792 {
10793     static char buf[MSG_SIZ];
10794     char *p;
10795
10796     p = strchr(str, ' ');
10797     if (p == NULL) return str;
10798     strncpy(buf, str, p - str);
10799     buf[p - str] = NULLCHAR;
10800     return buf;
10801 }
10802
10803 #define PGN_MAX_LINE 75
10804
10805 #define PGN_SIDE_WHITE  0
10806 #define PGN_SIDE_BLACK  1
10807
10808 /* [AS] */
10809 static int FindFirstMoveOutOfBook( int side )
10810 {
10811     int result = -1;
10812
10813     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10814         int index = backwardMostMove;
10815         int has_book_hit = 0;
10816
10817         if( (index % 2) != side ) {
10818             index++;
10819         }
10820
10821         while( index < forwardMostMove ) {
10822             /* Check to see if engine is in book */
10823             int depth = pvInfoList[index].depth;
10824             int score = pvInfoList[index].score;
10825             int in_book = 0;
10826
10827             if( depth <= 2 ) {
10828                 in_book = 1;
10829             }
10830             else if( score == 0 && depth == 63 ) {
10831                 in_book = 1; /* Zappa */
10832             }
10833             else if( score == 2 && depth == 99 ) {
10834                 in_book = 1; /* Abrok */
10835             }
10836
10837             has_book_hit += in_book;
10838
10839             if( ! in_book ) {
10840                 result = index;
10841
10842                 break;
10843             }
10844
10845             index += 2;
10846         }
10847     }
10848
10849     return result;
10850 }
10851
10852 /* [AS] */
10853 void GetOutOfBookInfo( char * buf )
10854 {
10855     int oob[2];
10856     int i;
10857     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10858
10859     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10860     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10861
10862     *buf = '\0';
10863
10864     if( oob[0] >= 0 || oob[1] >= 0 ) {
10865         for( i=0; i<2; i++ ) {
10866             int idx = oob[i];
10867
10868             if( idx >= 0 ) {
10869                 if( i > 0 && oob[0] >= 0 ) {
10870                     strcat( buf, "   " );
10871                 }
10872
10873                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10874                 sprintf( buf+strlen(buf), "%s%.2f",
10875                     pvInfoList[idx].score >= 0 ? "+" : "",
10876                     pvInfoList[idx].score / 100.0 );
10877             }
10878         }
10879     }
10880 }
10881
10882 /* Save game in PGN style and close the file */
10883 int
10884 SaveGamePGN(f)
10885      FILE *f;
10886 {
10887     int i, offset, linelen, newblock;
10888     time_t tm;
10889 //    char *movetext;
10890     char numtext[32];
10891     int movelen, numlen, blank;
10892     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10893
10894     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10895
10896     tm = time((time_t *) NULL);
10897
10898     PrintPGNTags(f, &gameInfo);
10899
10900     if (backwardMostMove > 0 || startedFromSetupPosition) {
10901         char *fen = PositionToFEN(backwardMostMove, NULL);
10902         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10903         fprintf(f, "\n{--------------\n");
10904         PrintPosition(f, backwardMostMove);
10905         fprintf(f, "--------------}\n");
10906         free(fen);
10907     }
10908     else {
10909         /* [AS] Out of book annotation */
10910         if( appData.saveOutOfBookInfo ) {
10911             char buf[64];
10912
10913             GetOutOfBookInfo( buf );
10914
10915             if( buf[0] != '\0' ) {
10916                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10917             }
10918         }
10919
10920         fprintf(f, "\n");
10921     }
10922
10923     i = backwardMostMove;
10924     linelen = 0;
10925     newblock = TRUE;
10926
10927     while (i < forwardMostMove) {
10928         /* Print comments preceding this move */
10929         if (commentList[i] != NULL) {
10930             if (linelen > 0) fprintf(f, "\n");
10931             fprintf(f, "%s", commentList[i]);
10932             linelen = 0;
10933             newblock = TRUE;
10934         }
10935
10936         /* Format move number */
10937         if ((i % 2) == 0)
10938           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10939         else
10940           if (newblock)
10941             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10942           else
10943             numtext[0] = NULLCHAR;
10944
10945         numlen = strlen(numtext);
10946         newblock = FALSE;
10947
10948         /* Print move number */
10949         blank = linelen > 0 && numlen > 0;
10950         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10951             fprintf(f, "\n");
10952             linelen = 0;
10953             blank = 0;
10954         }
10955         if (blank) {
10956             fprintf(f, " ");
10957             linelen++;
10958         }
10959         fprintf(f, "%s", numtext);
10960         linelen += numlen;
10961
10962         /* Get move */
10963         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10964         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10965
10966         /* Print move */
10967         blank = linelen > 0 && movelen > 0;
10968         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10969             fprintf(f, "\n");
10970             linelen = 0;
10971             blank = 0;
10972         }
10973         if (blank) {
10974             fprintf(f, " ");
10975             linelen++;
10976         }
10977         fprintf(f, "%s", move_buffer);
10978         linelen += movelen;
10979
10980         /* [AS] Add PV info if present */
10981         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10982             /* [HGM] add time */
10983             char buf[MSG_SIZ]; int seconds;
10984
10985             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10986
10987             if( seconds <= 0)
10988               buf[0] = 0;
10989             else
10990               if( seconds < 30 )
10991                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10992               else
10993                 {
10994                   seconds = (seconds + 4)/10; // round to full seconds
10995                   if( seconds < 60 )
10996                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10997                   else
10998                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10999                 }
11000
11001             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11002                       pvInfoList[i].score >= 0 ? "+" : "",
11003                       pvInfoList[i].score / 100.0,
11004                       pvInfoList[i].depth,
11005                       buf );
11006
11007             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11008
11009             /* Print score/depth */
11010             blank = linelen > 0 && movelen > 0;
11011             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11012                 fprintf(f, "\n");
11013                 linelen = 0;
11014                 blank = 0;
11015             }
11016             if (blank) {
11017                 fprintf(f, " ");
11018                 linelen++;
11019             }
11020             fprintf(f, "%s", move_buffer);
11021             linelen += movelen;
11022         }
11023
11024         i++;
11025     }
11026
11027     /* Start a new line */
11028     if (linelen > 0) fprintf(f, "\n");
11029
11030     /* Print comments after last move */
11031     if (commentList[i] != NULL) {
11032         fprintf(f, "%s\n", commentList[i]);
11033     }
11034
11035     /* Print result */
11036     if (gameInfo.resultDetails != NULL &&
11037         gameInfo.resultDetails[0] != NULLCHAR) {
11038         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11039                 PGNResult(gameInfo.result));
11040     } else {
11041         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11042     }
11043
11044     fclose(f);
11045     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11046     return TRUE;
11047 }
11048
11049 /* Save game in old style and close the file */
11050 int
11051 SaveGameOldStyle(f)
11052      FILE *f;
11053 {
11054     int i, offset;
11055     time_t tm;
11056
11057     tm = time((time_t *) NULL);
11058
11059     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11060     PrintOpponents(f);
11061
11062     if (backwardMostMove > 0 || startedFromSetupPosition) {
11063         fprintf(f, "\n[--------------\n");
11064         PrintPosition(f, backwardMostMove);
11065         fprintf(f, "--------------]\n");
11066     } else {
11067         fprintf(f, "\n");
11068     }
11069
11070     i = backwardMostMove;
11071     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11072
11073     while (i < forwardMostMove) {
11074         if (commentList[i] != NULL) {
11075             fprintf(f, "[%s]\n", commentList[i]);
11076         }
11077
11078         if ((i % 2) == 1) {
11079             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11080             i++;
11081         } else {
11082             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11083             i++;
11084             if (commentList[i] != NULL) {
11085                 fprintf(f, "\n");
11086                 continue;
11087             }
11088             if (i >= forwardMostMove) {
11089                 fprintf(f, "\n");
11090                 break;
11091             }
11092             fprintf(f, "%s\n", parseList[i]);
11093             i++;
11094         }
11095     }
11096
11097     if (commentList[i] != NULL) {
11098         fprintf(f, "[%s]\n", commentList[i]);
11099     }
11100
11101     /* This isn't really the old style, but it's close enough */
11102     if (gameInfo.resultDetails != NULL &&
11103         gameInfo.resultDetails[0] != NULLCHAR) {
11104         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11105                 gameInfo.resultDetails);
11106     } else {
11107         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11108     }
11109
11110     fclose(f);
11111     return TRUE;
11112 }
11113
11114 /* Save the current game to open file f and close the file */
11115 int
11116 SaveGame(f, dummy, dummy2)
11117      FILE *f;
11118      int dummy;
11119      char *dummy2;
11120 {
11121     if (gameMode == EditPosition) EditPositionDone(TRUE);
11122     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11123     if (appData.oldSaveStyle)
11124       return SaveGameOldStyle(f);
11125     else
11126       return SaveGamePGN(f);
11127 }
11128
11129 /* Save the current position to the given file */
11130 int
11131 SavePositionToFile(filename)
11132      char *filename;
11133 {
11134     FILE *f;
11135     char buf[MSG_SIZ];
11136
11137     if (strcmp(filename, "-") == 0) {
11138         return SavePosition(stdout, 0, NULL);
11139     } else {
11140         f = fopen(filename, "a");
11141         if (f == NULL) {
11142             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11143             DisplayError(buf, errno);
11144             return FALSE;
11145         } else {
11146             SavePosition(f, 0, NULL);
11147             return TRUE;
11148         }
11149     }
11150 }
11151
11152 /* Save the current position to the given open file and close the file */
11153 int
11154 SavePosition(f, dummy, dummy2)
11155      FILE *f;
11156      int dummy;
11157      char *dummy2;
11158 {
11159     time_t tm;
11160     char *fen;
11161
11162     if (gameMode == EditPosition) EditPositionDone(TRUE);
11163     if (appData.oldSaveStyle) {
11164         tm = time((time_t *) NULL);
11165
11166         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11167         PrintOpponents(f);
11168         fprintf(f, "[--------------\n");
11169         PrintPosition(f, currentMove);
11170         fprintf(f, "--------------]\n");
11171     } else {
11172         fen = PositionToFEN(currentMove, NULL);
11173         fprintf(f, "%s\n", fen);
11174         free(fen);
11175     }
11176     fclose(f);
11177     return TRUE;
11178 }
11179
11180 void
11181 ReloadCmailMsgEvent(unregister)
11182      int unregister;
11183 {
11184 #if !WIN32
11185     static char *inFilename = NULL;
11186     static char *outFilename;
11187     int i;
11188     struct stat inbuf, outbuf;
11189     int status;
11190
11191     /* Any registered moves are unregistered if unregister is set, */
11192     /* i.e. invoked by the signal handler */
11193     if (unregister) {
11194         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11195             cmailMoveRegistered[i] = FALSE;
11196             if (cmailCommentList[i] != NULL) {
11197                 free(cmailCommentList[i]);
11198                 cmailCommentList[i] = NULL;
11199             }
11200         }
11201         nCmailMovesRegistered = 0;
11202     }
11203
11204     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11205         cmailResult[i] = CMAIL_NOT_RESULT;
11206     }
11207     nCmailResults = 0;
11208
11209     if (inFilename == NULL) {
11210         /* Because the filenames are static they only get malloced once  */
11211         /* and they never get freed                                      */
11212         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11213         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11214
11215         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11216         sprintf(outFilename, "%s.out", appData.cmailGameName);
11217     }
11218
11219     status = stat(outFilename, &outbuf);
11220     if (status < 0) {
11221         cmailMailedMove = FALSE;
11222     } else {
11223         status = stat(inFilename, &inbuf);
11224         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11225     }
11226
11227     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11228        counts the games, notes how each one terminated, etc.
11229
11230        It would be nice to remove this kludge and instead gather all
11231        the information while building the game list.  (And to keep it
11232        in the game list nodes instead of having a bunch of fixed-size
11233        parallel arrays.)  Note this will require getting each game's
11234        termination from the PGN tags, as the game list builder does
11235        not process the game moves.  --mann
11236        */
11237     cmailMsgLoaded = TRUE;
11238     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11239
11240     /* Load first game in the file or popup game menu */
11241     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11242
11243 #endif /* !WIN32 */
11244     return;
11245 }
11246
11247 int
11248 RegisterMove()
11249 {
11250     FILE *f;
11251     char string[MSG_SIZ];
11252
11253     if (   cmailMailedMove
11254         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11255         return TRUE;            /* Allow free viewing  */
11256     }
11257
11258     /* Unregister move to ensure that we don't leave RegisterMove        */
11259     /* with the move registered when the conditions for registering no   */
11260     /* longer hold                                                       */
11261     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11262         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11263         nCmailMovesRegistered --;
11264
11265         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11266           {
11267               free(cmailCommentList[lastLoadGameNumber - 1]);
11268               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11269           }
11270     }
11271
11272     if (cmailOldMove == -1) {
11273         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11274         return FALSE;
11275     }
11276
11277     if (currentMove > cmailOldMove + 1) {
11278         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11279         return FALSE;
11280     }
11281
11282     if (currentMove < cmailOldMove) {
11283         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11284         return FALSE;
11285     }
11286
11287     if (forwardMostMove > currentMove) {
11288         /* Silently truncate extra moves */
11289         TruncateGame();
11290     }
11291
11292     if (   (currentMove == cmailOldMove + 1)
11293         || (   (currentMove == cmailOldMove)
11294             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11295                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11296         if (gameInfo.result != GameUnfinished) {
11297             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11298         }
11299
11300         if (commentList[currentMove] != NULL) {
11301             cmailCommentList[lastLoadGameNumber - 1]
11302               = StrSave(commentList[currentMove]);
11303         }
11304         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11305
11306         if (appData.debugMode)
11307           fprintf(debugFP, "Saving %s for game %d\n",
11308                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11309
11310         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11311
11312         f = fopen(string, "w");
11313         if (appData.oldSaveStyle) {
11314             SaveGameOldStyle(f); /* also closes the file */
11315
11316             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11317             f = fopen(string, "w");
11318             SavePosition(f, 0, NULL); /* also closes the file */
11319         } else {
11320             fprintf(f, "{--------------\n");
11321             PrintPosition(f, currentMove);
11322             fprintf(f, "--------------}\n\n");
11323
11324             SaveGame(f, 0, NULL); /* also closes the file*/
11325         }
11326
11327         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11328         nCmailMovesRegistered ++;
11329     } else if (nCmailGames == 1) {
11330         DisplayError(_("You have not made a move yet"), 0);
11331         return FALSE;
11332     }
11333
11334     return TRUE;
11335 }
11336
11337 void
11338 MailMoveEvent()
11339 {
11340 #if !WIN32
11341     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11342     FILE *commandOutput;
11343     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11344     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11345     int nBuffers;
11346     int i;
11347     int archived;
11348     char *arcDir;
11349
11350     if (! cmailMsgLoaded) {
11351         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11352         return;
11353     }
11354
11355     if (nCmailGames == nCmailResults) {
11356         DisplayError(_("No unfinished games"), 0);
11357         return;
11358     }
11359
11360 #if CMAIL_PROHIBIT_REMAIL
11361     if (cmailMailedMove) {
11362       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);
11363         DisplayError(msg, 0);
11364         return;
11365     }
11366 #endif
11367
11368     if (! (cmailMailedMove || RegisterMove())) return;
11369
11370     if (   cmailMailedMove
11371         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11372       snprintf(string, MSG_SIZ, partCommandString,
11373                appData.debugMode ? " -v" : "", appData.cmailGameName);
11374         commandOutput = popen(string, "r");
11375
11376         if (commandOutput == NULL) {
11377             DisplayError(_("Failed to invoke cmail"), 0);
11378         } else {
11379             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11380                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11381             }
11382             if (nBuffers > 1) {
11383                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11384                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11385                 nBytes = MSG_SIZ - 1;
11386             } else {
11387                 (void) memcpy(msg, buffer, nBytes);
11388             }
11389             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11390
11391             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11392                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11393
11394                 archived = TRUE;
11395                 for (i = 0; i < nCmailGames; i ++) {
11396                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11397                         archived = FALSE;
11398                     }
11399                 }
11400                 if (   archived
11401                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11402                         != NULL)) {
11403                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11404                            arcDir,
11405                            appData.cmailGameName,
11406                            gameInfo.date);
11407                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11408                     cmailMsgLoaded = FALSE;
11409                 }
11410             }
11411
11412             DisplayInformation(msg);
11413             pclose(commandOutput);
11414         }
11415     } else {
11416         if ((*cmailMsg) != '\0') {
11417             DisplayInformation(cmailMsg);
11418         }
11419     }
11420
11421     return;
11422 #endif /* !WIN32 */
11423 }
11424
11425 char *
11426 CmailMsg()
11427 {
11428 #if WIN32
11429     return NULL;
11430 #else
11431     int  prependComma = 0;
11432     char number[5];
11433     char string[MSG_SIZ];       /* Space for game-list */
11434     int  i;
11435
11436     if (!cmailMsgLoaded) return "";
11437
11438     if (cmailMailedMove) {
11439       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11440     } else {
11441         /* Create a list of games left */
11442       snprintf(string, MSG_SIZ, "[");
11443         for (i = 0; i < nCmailGames; i ++) {
11444             if (! (   cmailMoveRegistered[i]
11445                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11446                 if (prependComma) {
11447                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11448                 } else {
11449                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11450                     prependComma = 1;
11451                 }
11452
11453                 strcat(string, number);
11454             }
11455         }
11456         strcat(string, "]");
11457
11458         if (nCmailMovesRegistered + nCmailResults == 0) {
11459             switch (nCmailGames) {
11460               case 1:
11461                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11462                 break;
11463
11464               case 2:
11465                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11466                 break;
11467
11468               default:
11469                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11470                          nCmailGames);
11471                 break;
11472             }
11473         } else {
11474             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11475               case 1:
11476                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11477                          string);
11478                 break;
11479
11480               case 0:
11481                 if (nCmailResults == nCmailGames) {
11482                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11483                 } else {
11484                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11485                 }
11486                 break;
11487
11488               default:
11489                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11490                          string);
11491             }
11492         }
11493     }
11494     return cmailMsg;
11495 #endif /* WIN32 */
11496 }
11497
11498 void
11499 ResetGameEvent()
11500 {
11501     if (gameMode == Training)
11502       SetTrainingModeOff();
11503
11504     Reset(TRUE, TRUE);
11505     cmailMsgLoaded = FALSE;
11506     if (appData.icsActive) {
11507       SendToICS(ics_prefix);
11508       SendToICS("refresh\n");
11509     }
11510 }
11511
11512 void
11513 ExitEvent(status)
11514      int status;
11515 {
11516     exiting++;
11517     if (exiting > 2) {
11518       /* Give up on clean exit */
11519       exit(status);
11520     }
11521     if (exiting > 1) {
11522       /* Keep trying for clean exit */
11523       return;
11524     }
11525
11526     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11527
11528     if (telnetISR != NULL) {
11529       RemoveInputSource(telnetISR);
11530     }
11531     if (icsPR != NoProc) {
11532       DestroyChildProcess(icsPR, TRUE);
11533     }
11534
11535     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11536     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11537
11538     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11539     /* make sure this other one finishes before killing it!                  */
11540     if(endingGame) { int count = 0;
11541         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11542         while(endingGame && count++ < 10) DoSleep(1);
11543         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11544     }
11545
11546     /* Kill off chess programs */
11547     if (first.pr != NoProc) {
11548         ExitAnalyzeMode();
11549
11550         DoSleep( appData.delayBeforeQuit );
11551         SendToProgram("quit\n", &first);
11552         DoSleep( appData.delayAfterQuit );
11553         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11554     }
11555     if (second.pr != NoProc) {
11556         DoSleep( appData.delayBeforeQuit );
11557         SendToProgram("quit\n", &second);
11558         DoSleep( appData.delayAfterQuit );
11559         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11560     }
11561     if (first.isr != NULL) {
11562         RemoveInputSource(first.isr);
11563     }
11564     if (second.isr != NULL) {
11565         RemoveInputSource(second.isr);
11566     }
11567
11568     ShutDownFrontEnd();
11569     exit(status);
11570 }
11571
11572 void
11573 PauseEvent()
11574 {
11575     if (appData.debugMode)
11576         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11577     if (pausing) {
11578         pausing = FALSE;
11579         ModeHighlight();
11580         if (gameMode == MachinePlaysWhite ||
11581             gameMode == MachinePlaysBlack) {
11582             StartClocks();
11583         } else {
11584             DisplayBothClocks();
11585         }
11586         if (gameMode == PlayFromGameFile) {
11587             if (appData.timeDelay >= 0)
11588                 AutoPlayGameLoop();
11589         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11590             Reset(FALSE, TRUE);
11591             SendToICS(ics_prefix);
11592             SendToICS("refresh\n");
11593         } else if (currentMove < forwardMostMove) {
11594             ForwardInner(forwardMostMove);
11595         }
11596         pauseExamInvalid = FALSE;
11597     } else {
11598         switch (gameMode) {
11599           default:
11600             return;
11601           case IcsExamining:
11602             pauseExamForwardMostMove = forwardMostMove;
11603             pauseExamInvalid = FALSE;
11604             /* fall through */
11605           case IcsObserving:
11606           case IcsPlayingWhite:
11607           case IcsPlayingBlack:
11608             pausing = TRUE;
11609             ModeHighlight();
11610             return;
11611           case PlayFromGameFile:
11612             (void) StopLoadGameTimer();
11613             pausing = TRUE;
11614             ModeHighlight();
11615             break;
11616           case BeginningOfGame:
11617             if (appData.icsActive) return;
11618             /* else fall through */
11619           case MachinePlaysWhite:
11620           case MachinePlaysBlack:
11621           case TwoMachinesPlay:
11622             if (forwardMostMove == 0)
11623               return;           /* don't pause if no one has moved */
11624             if ((gameMode == MachinePlaysWhite &&
11625                  !WhiteOnMove(forwardMostMove)) ||
11626                 (gameMode == MachinePlaysBlack &&
11627                  WhiteOnMove(forwardMostMove))) {
11628                 StopClocks();
11629             }
11630             pausing = TRUE;
11631             ModeHighlight();
11632             break;
11633         }
11634     }
11635 }
11636
11637 void
11638 EditCommentEvent()
11639 {
11640     char title[MSG_SIZ];
11641
11642     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11643       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11644     } else {
11645       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11646                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11647                parseList[currentMove - 1]);
11648     }
11649
11650     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11651 }
11652
11653
11654 void
11655 EditTagsEvent()
11656 {
11657     char *tags = PGNTags(&gameInfo);
11658     EditTagsPopUp(tags, NULL);
11659     free(tags);
11660 }
11661
11662 void
11663 AnalyzeModeEvent()
11664 {
11665     if (appData.noChessProgram || gameMode == AnalyzeMode)
11666       return;
11667
11668     if (gameMode != AnalyzeFile) {
11669         if (!appData.icsEngineAnalyze) {
11670                EditGameEvent();
11671                if (gameMode != EditGame) return;
11672         }
11673         ResurrectChessProgram();
11674         SendToProgram("analyze\n", &first);
11675         first.analyzing = TRUE;
11676         /*first.maybeThinking = TRUE;*/
11677         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11678         EngineOutputPopUp();
11679     }
11680     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11681     pausing = FALSE;
11682     ModeHighlight();
11683     SetGameInfo();
11684
11685     StartAnalysisClock();
11686     GetTimeMark(&lastNodeCountTime);
11687     lastNodeCount = 0;
11688 }
11689
11690 void
11691 AnalyzeFileEvent()
11692 {
11693     if (appData.noChessProgram || gameMode == AnalyzeFile)
11694       return;
11695
11696     if (gameMode != AnalyzeMode) {
11697         EditGameEvent();
11698         if (gameMode != EditGame) return;
11699         ResurrectChessProgram();
11700         SendToProgram("analyze\n", &first);
11701         first.analyzing = TRUE;
11702         /*first.maybeThinking = TRUE;*/
11703         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11704         EngineOutputPopUp();
11705     }
11706     gameMode = AnalyzeFile;
11707     pausing = FALSE;
11708     ModeHighlight();
11709     SetGameInfo();
11710
11711     StartAnalysisClock();
11712     GetTimeMark(&lastNodeCountTime);
11713     lastNodeCount = 0;
11714 }
11715
11716 void
11717 MachineWhiteEvent()
11718 {
11719     char buf[MSG_SIZ];
11720     char *bookHit = NULL;
11721
11722     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11723       return;
11724
11725
11726     if (gameMode == PlayFromGameFile ||
11727         gameMode == TwoMachinesPlay  ||
11728         gameMode == Training         ||
11729         gameMode == AnalyzeMode      ||
11730         gameMode == EndOfGame)
11731         EditGameEvent();
11732
11733     if (gameMode == EditPosition)
11734         EditPositionDone(TRUE);
11735
11736     if (!WhiteOnMove(currentMove)) {
11737         DisplayError(_("It is not White's turn"), 0);
11738         return;
11739     }
11740
11741     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11742       ExitAnalyzeMode();
11743
11744     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11745         gameMode == AnalyzeFile)
11746         TruncateGame();
11747
11748     ResurrectChessProgram();    /* in case it isn't running */
11749     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11750         gameMode = MachinePlaysWhite;
11751         ResetClocks();
11752     } else
11753     gameMode = MachinePlaysWhite;
11754     pausing = FALSE;
11755     ModeHighlight();
11756     SetGameInfo();
11757     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11758     DisplayTitle(buf);
11759     if (first.sendName) {
11760       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11761       SendToProgram(buf, &first);
11762     }
11763     if (first.sendTime) {
11764       if (first.useColors) {
11765         SendToProgram("black\n", &first); /*gnu kludge*/
11766       }
11767       SendTimeRemaining(&first, TRUE);
11768     }
11769     if (first.useColors) {
11770       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11771     }
11772     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11773     SetMachineThinkingEnables();
11774     first.maybeThinking = TRUE;
11775     StartClocks();
11776     firstMove = FALSE;
11777
11778     if (appData.autoFlipView && !flipView) {
11779       flipView = !flipView;
11780       DrawPosition(FALSE, NULL);
11781       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11782     }
11783
11784     if(bookHit) { // [HGM] book: simulate book reply
11785         static char bookMove[MSG_SIZ]; // a bit generous?
11786
11787         programStats.nodes = programStats.depth = programStats.time =
11788         programStats.score = programStats.got_only_move = 0;
11789         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11790
11791         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11792         strcat(bookMove, bookHit);
11793         HandleMachineMove(bookMove, &first);
11794     }
11795 }
11796
11797 void
11798 MachineBlackEvent()
11799 {
11800   char buf[MSG_SIZ];
11801   char *bookHit = NULL;
11802
11803     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11804         return;
11805
11806
11807     if (gameMode == PlayFromGameFile ||
11808         gameMode == TwoMachinesPlay  ||
11809         gameMode == Training         ||
11810         gameMode == AnalyzeMode      ||
11811         gameMode == EndOfGame)
11812         EditGameEvent();
11813
11814     if (gameMode == EditPosition)
11815         EditPositionDone(TRUE);
11816
11817     if (WhiteOnMove(currentMove)) {
11818         DisplayError(_("It is not Black's turn"), 0);
11819         return;
11820     }
11821
11822     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11823       ExitAnalyzeMode();
11824
11825     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11826         gameMode == AnalyzeFile)
11827         TruncateGame();
11828
11829     ResurrectChessProgram();    /* in case it isn't running */
11830     gameMode = MachinePlaysBlack;
11831     pausing = FALSE;
11832     ModeHighlight();
11833     SetGameInfo();
11834     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11835     DisplayTitle(buf);
11836     if (first.sendName) {
11837       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11838       SendToProgram(buf, &first);
11839     }
11840     if (first.sendTime) {
11841       if (first.useColors) {
11842         SendToProgram("white\n", &first); /*gnu kludge*/
11843       }
11844       SendTimeRemaining(&first, FALSE);
11845     }
11846     if (first.useColors) {
11847       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11848     }
11849     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11850     SetMachineThinkingEnables();
11851     first.maybeThinking = TRUE;
11852     StartClocks();
11853
11854     if (appData.autoFlipView && flipView) {
11855       flipView = !flipView;
11856       DrawPosition(FALSE, NULL);
11857       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11858     }
11859     if(bookHit) { // [HGM] book: simulate book reply
11860         static char bookMove[MSG_SIZ]; // a bit generous?
11861
11862         programStats.nodes = programStats.depth = programStats.time =
11863         programStats.score = programStats.got_only_move = 0;
11864         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11865
11866         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11867         strcat(bookMove, bookHit);
11868         HandleMachineMove(bookMove, &first);
11869     }
11870 }
11871
11872
11873 void
11874 DisplayTwoMachinesTitle()
11875 {
11876     char buf[MSG_SIZ];
11877     if (appData.matchGames > 0) {
11878         if (first.twoMachinesColor[0] == 'w') {
11879           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11880                    gameInfo.white, gameInfo.black,
11881                    first.matchWins, second.matchWins,
11882                    matchGame - 1 - (first.matchWins + second.matchWins));
11883         } else {
11884           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11885                    gameInfo.white, gameInfo.black,
11886                    second.matchWins, first.matchWins,
11887                    matchGame - 1 - (first.matchWins + second.matchWins));
11888         }
11889     } else {
11890       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11891     }
11892     DisplayTitle(buf);
11893 }
11894
11895 void
11896 SettingsMenuIfReady()
11897 {
11898   if (second.lastPing != second.lastPong) {
11899     DisplayMessage("", _("Waiting for second chess program"));
11900     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11901     return;
11902   }
11903   ThawUI();
11904   DisplayMessage("", "");
11905   SettingsPopUp(&second);
11906 }
11907
11908 int
11909 WaitForSecond(DelayedEventCallback retry)
11910 {
11911     if (second.pr == NULL) {
11912         StartChessProgram(&second);
11913         if (second.protocolVersion == 1) {
11914           retry();
11915         } else {
11916           /* kludge: allow timeout for initial "feature" command */
11917           FreezeUI();
11918           DisplayMessage("", _("Starting second chess program"));
11919           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11920         }
11921         return 1;
11922     }
11923     return 0;
11924 }
11925
11926 void
11927 TwoMachinesEvent P((void))
11928 {
11929     int i;
11930     char buf[MSG_SIZ];
11931     ChessProgramState *onmove;
11932     char *bookHit = NULL;
11933
11934     if (appData.noChessProgram) return;
11935
11936     switch (gameMode) {
11937       case TwoMachinesPlay:
11938         return;
11939       case MachinePlaysWhite:
11940       case MachinePlaysBlack:
11941         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11942             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11943             return;
11944         }
11945         /* fall through */
11946       case BeginningOfGame:
11947       case PlayFromGameFile:
11948       case EndOfGame:
11949         EditGameEvent();
11950         if (gameMode != EditGame) return;
11951         break;
11952       case EditPosition:
11953         EditPositionDone(TRUE);
11954         break;
11955       case AnalyzeMode:
11956       case AnalyzeFile:
11957         ExitAnalyzeMode();
11958         break;
11959       case EditGame:
11960       default:
11961         break;
11962     }
11963
11964 //    forwardMostMove = currentMove;
11965     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11966     ResurrectChessProgram();    /* in case first program isn't running */
11967
11968     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11969     DisplayMessage("", "");
11970     InitChessProgram(&second, FALSE);
11971     SendToProgram("force\n", &second);
11972     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11973       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11974       return;
11975     }
11976     if (startedFromSetupPosition) {
11977         SendBoard(&second, backwardMostMove);
11978     if (appData.debugMode) {
11979         fprintf(debugFP, "Two Machines\n");
11980     }
11981     }
11982     for (i = backwardMostMove; i < forwardMostMove; i++) {
11983         SendMoveToProgram(i, &second);
11984     }
11985
11986     gameMode = TwoMachinesPlay;
11987     pausing = FALSE;
11988     ModeHighlight();
11989     SetGameInfo();
11990     DisplayTwoMachinesTitle();
11991     firstMove = TRUE;
11992     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11993         onmove = &first;
11994     } else {
11995         onmove = &second;
11996     }
11997
11998     SendToProgram(first.computerString, &first);
11999     if (first.sendName) {
12000       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12001       SendToProgram(buf, &first);
12002     }
12003     SendToProgram(second.computerString, &second);
12004     if (second.sendName) {
12005       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12006       SendToProgram(buf, &second);
12007     }
12008
12009     ResetClocks();
12010     if (!first.sendTime || !second.sendTime) {
12011         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12012         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12013     }
12014     if (onmove->sendTime) {
12015       if (onmove->useColors) {
12016         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12017       }
12018       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12019     }
12020     if (onmove->useColors) {
12021       SendToProgram(onmove->twoMachinesColor, onmove);
12022     }
12023     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12024 //    SendToProgram("go\n", onmove);
12025     onmove->maybeThinking = TRUE;
12026     SetMachineThinkingEnables();
12027
12028     StartClocks();
12029
12030     if(bookHit) { // [HGM] book: simulate book reply
12031         static char bookMove[MSG_SIZ]; // a bit generous?
12032
12033         programStats.nodes = programStats.depth = programStats.time =
12034         programStats.score = programStats.got_only_move = 0;
12035         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12036
12037         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12038         strcat(bookMove, bookHit);
12039         savedMessage = bookMove; // args for deferred call
12040         savedState = onmove;
12041         ScheduleDelayedEvent(DeferredBookMove, 1);
12042     }
12043 }
12044
12045 void
12046 TrainingEvent()
12047 {
12048     if (gameMode == Training) {
12049       SetTrainingModeOff();
12050       gameMode = PlayFromGameFile;
12051       DisplayMessage("", _("Training mode off"));
12052     } else {
12053       gameMode = Training;
12054       animateTraining = appData.animate;
12055
12056       /* make sure we are not already at the end of the game */
12057       if (currentMove < forwardMostMove) {
12058         SetTrainingModeOn();
12059         DisplayMessage("", _("Training mode on"));
12060       } else {
12061         gameMode = PlayFromGameFile;
12062         DisplayError(_("Already at end of game"), 0);
12063       }
12064     }
12065     ModeHighlight();
12066 }
12067
12068 void
12069 IcsClientEvent()
12070 {
12071     if (!appData.icsActive) return;
12072     switch (gameMode) {
12073       case IcsPlayingWhite:
12074       case IcsPlayingBlack:
12075       case IcsObserving:
12076       case IcsIdle:
12077       case BeginningOfGame:
12078       case IcsExamining:
12079         return;
12080
12081       case EditGame:
12082         break;
12083
12084       case EditPosition:
12085         EditPositionDone(TRUE);
12086         break;
12087
12088       case AnalyzeMode:
12089       case AnalyzeFile:
12090         ExitAnalyzeMode();
12091         break;
12092
12093       default:
12094         EditGameEvent();
12095         break;
12096     }
12097
12098     gameMode = IcsIdle;
12099     ModeHighlight();
12100     return;
12101 }
12102
12103
12104 void
12105 EditGameEvent()
12106 {
12107     int i;
12108
12109     switch (gameMode) {
12110       case Training:
12111         SetTrainingModeOff();
12112         break;
12113       case MachinePlaysWhite:
12114       case MachinePlaysBlack:
12115       case BeginningOfGame:
12116         SendToProgram("force\n", &first);
12117         SetUserThinkingEnables();
12118         break;
12119       case PlayFromGameFile:
12120         (void) StopLoadGameTimer();
12121         if (gameFileFP != NULL) {
12122             gameFileFP = NULL;
12123         }
12124         break;
12125       case EditPosition:
12126         EditPositionDone(TRUE);
12127         break;
12128       case AnalyzeMode:
12129       case AnalyzeFile:
12130         ExitAnalyzeMode();
12131         SendToProgram("force\n", &first);
12132         break;
12133       case TwoMachinesPlay:
12134         GameEnds(EndOfFile, NULL, GE_PLAYER);
12135         ResurrectChessProgram();
12136         SetUserThinkingEnables();
12137         break;
12138       case EndOfGame:
12139         ResurrectChessProgram();
12140         break;
12141       case IcsPlayingBlack:
12142       case IcsPlayingWhite:
12143         DisplayError(_("Warning: You are still playing a game"), 0);
12144         break;
12145       case IcsObserving:
12146         DisplayError(_("Warning: You are still observing a game"), 0);
12147         break;
12148       case IcsExamining:
12149         DisplayError(_("Warning: You are still examining a game"), 0);
12150         break;
12151       case IcsIdle:
12152         break;
12153       case EditGame:
12154       default:
12155         return;
12156     }
12157
12158     pausing = FALSE;
12159     StopClocks();
12160     first.offeredDraw = second.offeredDraw = 0;
12161
12162     if (gameMode == PlayFromGameFile) {
12163         whiteTimeRemaining = timeRemaining[0][currentMove];
12164         blackTimeRemaining = timeRemaining[1][currentMove];
12165         DisplayTitle("");
12166     }
12167
12168     if (gameMode == MachinePlaysWhite ||
12169         gameMode == MachinePlaysBlack ||
12170         gameMode == TwoMachinesPlay ||
12171         gameMode == EndOfGame) {
12172         i = forwardMostMove;
12173         while (i > currentMove) {
12174             SendToProgram("undo\n", &first);
12175             i--;
12176         }
12177         whiteTimeRemaining = timeRemaining[0][currentMove];
12178         blackTimeRemaining = timeRemaining[1][currentMove];
12179         DisplayBothClocks();
12180         if (whiteFlag || blackFlag) {
12181             whiteFlag = blackFlag = 0;
12182         }
12183         DisplayTitle("");
12184     }
12185
12186     gameMode = EditGame;
12187     ModeHighlight();
12188     SetGameInfo();
12189 }
12190
12191
12192 void
12193 EditPositionEvent()
12194 {
12195     if (gameMode == EditPosition) {
12196         EditGameEvent();
12197         return;
12198     }
12199
12200     EditGameEvent();
12201     if (gameMode != EditGame) return;
12202
12203     gameMode = EditPosition;
12204     ModeHighlight();
12205     SetGameInfo();
12206     if (currentMove > 0)
12207       CopyBoard(boards[0], boards[currentMove]);
12208
12209     blackPlaysFirst = !WhiteOnMove(currentMove);
12210     ResetClocks();
12211     currentMove = forwardMostMove = backwardMostMove = 0;
12212     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12213     DisplayMove(-1);
12214 }
12215
12216 void
12217 ExitAnalyzeMode()
12218 {
12219     /* [DM] icsEngineAnalyze - possible call from other functions */
12220     if (appData.icsEngineAnalyze) {
12221         appData.icsEngineAnalyze = FALSE;
12222
12223         DisplayMessage("",_("Close ICS engine analyze..."));
12224     }
12225     if (first.analysisSupport && first.analyzing) {
12226       SendToProgram("exit\n", &first);
12227       first.analyzing = FALSE;
12228     }
12229     thinkOutput[0] = NULLCHAR;
12230 }
12231
12232 void
12233 EditPositionDone(Boolean fakeRights)
12234 {
12235     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12236
12237     startedFromSetupPosition = TRUE;
12238     InitChessProgram(&first, FALSE);
12239     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12240       boards[0][EP_STATUS] = EP_NONE;
12241       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12242     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12243         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12244         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12245       } else boards[0][CASTLING][2] = NoRights;
12246     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12247         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12248         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12249       } else boards[0][CASTLING][5] = NoRights;
12250     }
12251     SendToProgram("force\n", &first);
12252     if (blackPlaysFirst) {
12253         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12254         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12255         currentMove = forwardMostMove = backwardMostMove = 1;
12256         CopyBoard(boards[1], boards[0]);
12257     } else {
12258         currentMove = forwardMostMove = backwardMostMove = 0;
12259     }
12260     SendBoard(&first, forwardMostMove);
12261     if (appData.debugMode) {
12262         fprintf(debugFP, "EditPosDone\n");
12263     }
12264     DisplayTitle("");
12265     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12266     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12267     gameMode = EditGame;
12268     ModeHighlight();
12269     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12270     ClearHighlights(); /* [AS] */
12271 }
12272
12273 /* Pause for `ms' milliseconds */
12274 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12275 void
12276 TimeDelay(ms)
12277      long ms;
12278 {
12279     TimeMark m1, m2;
12280
12281     GetTimeMark(&m1);
12282     do {
12283         GetTimeMark(&m2);
12284     } while (SubtractTimeMarks(&m2, &m1) < ms);
12285 }
12286
12287 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12288 void
12289 SendMultiLineToICS(buf)
12290      char *buf;
12291 {
12292     char temp[MSG_SIZ+1], *p;
12293     int len;
12294
12295     len = strlen(buf);
12296     if (len > MSG_SIZ)
12297       len = MSG_SIZ;
12298
12299     strncpy(temp, buf, len);
12300     temp[len] = 0;
12301
12302     p = temp;
12303     while (*p) {
12304         if (*p == '\n' || *p == '\r')
12305           *p = ' ';
12306         ++p;
12307     }
12308
12309     strcat(temp, "\n");
12310     SendToICS(temp);
12311     SendToPlayer(temp, strlen(temp));
12312 }
12313
12314 void
12315 SetWhiteToPlayEvent()
12316 {
12317     if (gameMode == EditPosition) {
12318         blackPlaysFirst = FALSE;
12319         DisplayBothClocks();    /* works because currentMove is 0 */
12320     } else if (gameMode == IcsExamining) {
12321         SendToICS(ics_prefix);
12322         SendToICS("tomove white\n");
12323     }
12324 }
12325
12326 void
12327 SetBlackToPlayEvent()
12328 {
12329     if (gameMode == EditPosition) {
12330         blackPlaysFirst = TRUE;
12331         currentMove = 1;        /* kludge */
12332         DisplayBothClocks();
12333         currentMove = 0;
12334     } else if (gameMode == IcsExamining) {
12335         SendToICS(ics_prefix);
12336         SendToICS("tomove black\n");
12337     }
12338 }
12339
12340 void
12341 EditPositionMenuEvent(selection, x, y)
12342      ChessSquare selection;
12343      int x, y;
12344 {
12345     char buf[MSG_SIZ];
12346     ChessSquare piece = boards[0][y][x];
12347
12348     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12349
12350     switch (selection) {
12351       case ClearBoard:
12352         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12353             SendToICS(ics_prefix);
12354             SendToICS("bsetup clear\n");
12355         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12356             SendToICS(ics_prefix);
12357             SendToICS("clearboard\n");
12358         } else {
12359             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12360                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12361                 for (y = 0; y < BOARD_HEIGHT; y++) {
12362                     if (gameMode == IcsExamining) {
12363                         if (boards[currentMove][y][x] != EmptySquare) {
12364                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12365                                     AAA + x, ONE + y);
12366                             SendToICS(buf);
12367                         }
12368                     } else {
12369                         boards[0][y][x] = p;
12370                     }
12371                 }
12372             }
12373         }
12374         if (gameMode == EditPosition) {
12375             DrawPosition(FALSE, boards[0]);
12376         }
12377         break;
12378
12379       case WhitePlay:
12380         SetWhiteToPlayEvent();
12381         break;
12382
12383       case BlackPlay:
12384         SetBlackToPlayEvent();
12385         break;
12386
12387       case EmptySquare:
12388         if (gameMode == IcsExamining) {
12389             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12390             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12391             SendToICS(buf);
12392         } else {
12393             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12394                 if(x == BOARD_LEFT-2) {
12395                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12396                     boards[0][y][1] = 0;
12397                 } else
12398                 if(x == BOARD_RGHT+1) {
12399                     if(y >= gameInfo.holdingsSize) break;
12400                     boards[0][y][BOARD_WIDTH-2] = 0;
12401                 } else break;
12402             }
12403             boards[0][y][x] = EmptySquare;
12404             DrawPosition(FALSE, boards[0]);
12405         }
12406         break;
12407
12408       case PromotePiece:
12409         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12410            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12411             selection = (ChessSquare) (PROMOTED piece);
12412         } else if(piece == EmptySquare) selection = WhiteSilver;
12413         else selection = (ChessSquare)((int)piece - 1);
12414         goto defaultlabel;
12415
12416       case DemotePiece:
12417         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12418            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12419             selection = (ChessSquare) (DEMOTED piece);
12420         } else if(piece == EmptySquare) selection = BlackSilver;
12421         else selection = (ChessSquare)((int)piece + 1);
12422         goto defaultlabel;
12423
12424       case WhiteQueen:
12425       case BlackQueen:
12426         if(gameInfo.variant == VariantShatranj ||
12427            gameInfo.variant == VariantXiangqi  ||
12428            gameInfo.variant == VariantCourier  ||
12429            gameInfo.variant == VariantMakruk     )
12430             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12431         goto defaultlabel;
12432
12433       case WhiteKing:
12434       case BlackKing:
12435         if(gameInfo.variant == VariantXiangqi)
12436             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12437         if(gameInfo.variant == VariantKnightmate)
12438             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12439       default:
12440         defaultlabel:
12441         if (gameMode == IcsExamining) {
12442             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12443             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12444                      PieceToChar(selection), AAA + x, ONE + y);
12445             SendToICS(buf);
12446         } else {
12447             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12448                 int n;
12449                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12450                     n = PieceToNumber(selection - BlackPawn);
12451                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12452                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12453                     boards[0][BOARD_HEIGHT-1-n][1]++;
12454                 } else
12455                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12456                     n = PieceToNumber(selection);
12457                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12458                     boards[0][n][BOARD_WIDTH-1] = selection;
12459                     boards[0][n][BOARD_WIDTH-2]++;
12460                 }
12461             } else
12462             boards[0][y][x] = selection;
12463             DrawPosition(TRUE, boards[0]);
12464         }
12465         break;
12466     }
12467 }
12468
12469
12470 void
12471 DropMenuEvent(selection, x, y)
12472      ChessSquare selection;
12473      int x, y;
12474 {
12475     ChessMove moveType;
12476
12477     switch (gameMode) {
12478       case IcsPlayingWhite:
12479       case MachinePlaysBlack:
12480         if (!WhiteOnMove(currentMove)) {
12481             DisplayMoveError(_("It is Black's turn"));
12482             return;
12483         }
12484         moveType = WhiteDrop;
12485         break;
12486       case IcsPlayingBlack:
12487       case MachinePlaysWhite:
12488         if (WhiteOnMove(currentMove)) {
12489             DisplayMoveError(_("It is White's turn"));
12490             return;
12491         }
12492         moveType = BlackDrop;
12493         break;
12494       case EditGame:
12495         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12496         break;
12497       default:
12498         return;
12499     }
12500
12501     if (moveType == BlackDrop && selection < BlackPawn) {
12502       selection = (ChessSquare) ((int) selection
12503                                  + (int) BlackPawn - (int) WhitePawn);
12504     }
12505     if (boards[currentMove][y][x] != EmptySquare) {
12506         DisplayMoveError(_("That square is occupied"));
12507         return;
12508     }
12509
12510     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12511 }
12512
12513 void
12514 AcceptEvent()
12515 {
12516     /* Accept a pending offer of any kind from opponent */
12517
12518     if (appData.icsActive) {
12519         SendToICS(ics_prefix);
12520         SendToICS("accept\n");
12521     } else if (cmailMsgLoaded) {
12522         if (currentMove == cmailOldMove &&
12523             commentList[cmailOldMove] != NULL &&
12524             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12525                    "Black offers a draw" : "White offers a draw")) {
12526             TruncateGame();
12527             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12528             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12529         } else {
12530             DisplayError(_("There is no pending offer on this move"), 0);
12531             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12532         }
12533     } else {
12534         /* Not used for offers from chess program */
12535     }
12536 }
12537
12538 void
12539 DeclineEvent()
12540 {
12541     /* Decline a pending offer of any kind from opponent */
12542
12543     if (appData.icsActive) {
12544         SendToICS(ics_prefix);
12545         SendToICS("decline\n");
12546     } else if (cmailMsgLoaded) {
12547         if (currentMove == cmailOldMove &&
12548             commentList[cmailOldMove] != NULL &&
12549             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12550                    "Black offers a draw" : "White offers a draw")) {
12551 #ifdef NOTDEF
12552             AppendComment(cmailOldMove, "Draw declined", TRUE);
12553             DisplayComment(cmailOldMove - 1, "Draw declined");
12554 #endif /*NOTDEF*/
12555         } else {
12556             DisplayError(_("There is no pending offer on this move"), 0);
12557         }
12558     } else {
12559         /* Not used for offers from chess program */
12560     }
12561 }
12562
12563 void
12564 RematchEvent()
12565 {
12566     /* Issue ICS rematch command */
12567     if (appData.icsActive) {
12568         SendToICS(ics_prefix);
12569         SendToICS("rematch\n");
12570     }
12571 }
12572
12573 void
12574 CallFlagEvent()
12575 {
12576     /* Call your opponent's flag (claim a win on time) */
12577     if (appData.icsActive) {
12578         SendToICS(ics_prefix);
12579         SendToICS("flag\n");
12580     } else {
12581         switch (gameMode) {
12582           default:
12583             return;
12584           case MachinePlaysWhite:
12585             if (whiteFlag) {
12586                 if (blackFlag)
12587                   GameEnds(GameIsDrawn, "Both players ran out of time",
12588                            GE_PLAYER);
12589                 else
12590                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12591             } else {
12592                 DisplayError(_("Your opponent is not out of time"), 0);
12593             }
12594             break;
12595           case MachinePlaysBlack:
12596             if (blackFlag) {
12597                 if (whiteFlag)
12598                   GameEnds(GameIsDrawn, "Both players ran out of time",
12599                            GE_PLAYER);
12600                 else
12601                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12602             } else {
12603                 DisplayError(_("Your opponent is not out of time"), 0);
12604             }
12605             break;
12606         }
12607     }
12608 }
12609
12610 void
12611 ClockClick(int which)
12612 {       // [HGM] code moved to back-end from winboard.c
12613         if(which) { // black clock
12614           if (gameMode == EditPosition || gameMode == IcsExamining) {
12615             SetBlackToPlayEvent();
12616           } else if (gameMode == EditGame || shiftKey) {
12617             AdjustClock(which, -1);
12618           } else if (gameMode == IcsPlayingWhite ||
12619                      gameMode == MachinePlaysBlack) {
12620             CallFlagEvent();
12621           }
12622         } else { // white clock
12623           if (gameMode == EditPosition || gameMode == IcsExamining) {
12624             SetWhiteToPlayEvent();
12625           } else if (gameMode == EditGame || shiftKey) {
12626             AdjustClock(which, -1);
12627           } else if (gameMode == IcsPlayingBlack ||
12628                    gameMode == MachinePlaysWhite) {
12629             CallFlagEvent();
12630           }
12631         }
12632 }
12633
12634 void
12635 DrawEvent()
12636 {
12637     /* Offer draw or accept pending draw offer from opponent */
12638
12639     if (appData.icsActive) {
12640         /* Note: tournament rules require draw offers to be
12641            made after you make your move but before you punch
12642            your clock.  Currently ICS doesn't let you do that;
12643            instead, you immediately punch your clock after making
12644            a move, but you can offer a draw at any time. */
12645
12646         SendToICS(ics_prefix);
12647         SendToICS("draw\n");
12648         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12649     } else if (cmailMsgLoaded) {
12650         if (currentMove == cmailOldMove &&
12651             commentList[cmailOldMove] != NULL &&
12652             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12653                    "Black offers a draw" : "White offers a draw")) {
12654             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12655             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12656         } else if (currentMove == cmailOldMove + 1) {
12657             char *offer = WhiteOnMove(cmailOldMove) ?
12658               "White offers a draw" : "Black offers a draw";
12659             AppendComment(currentMove, offer, TRUE);
12660             DisplayComment(currentMove - 1, offer);
12661             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12662         } else {
12663             DisplayError(_("You must make your move before offering a draw"), 0);
12664             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12665         }
12666     } else if (first.offeredDraw) {
12667         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12668     } else {
12669         if (first.sendDrawOffers) {
12670             SendToProgram("draw\n", &first);
12671             userOfferedDraw = TRUE;
12672         }
12673     }
12674 }
12675
12676 void
12677 AdjournEvent()
12678 {
12679     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12680
12681     if (appData.icsActive) {
12682         SendToICS(ics_prefix);
12683         SendToICS("adjourn\n");
12684     } else {
12685         /* Currently GNU Chess doesn't offer or accept Adjourns */
12686     }
12687 }
12688
12689
12690 void
12691 AbortEvent()
12692 {
12693     /* Offer Abort or accept pending Abort offer from opponent */
12694
12695     if (appData.icsActive) {
12696         SendToICS(ics_prefix);
12697         SendToICS("abort\n");
12698     } else {
12699         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12700     }
12701 }
12702
12703 void
12704 ResignEvent()
12705 {
12706     /* Resign.  You can do this even if it's not your turn. */
12707
12708     if (appData.icsActive) {
12709         SendToICS(ics_prefix);
12710         SendToICS("resign\n");
12711     } else {
12712         switch (gameMode) {
12713           case MachinePlaysWhite:
12714             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12715             break;
12716           case MachinePlaysBlack:
12717             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12718             break;
12719           case EditGame:
12720             if (cmailMsgLoaded) {
12721                 TruncateGame();
12722                 if (WhiteOnMove(cmailOldMove)) {
12723                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12724                 } else {
12725                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12726                 }
12727                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12728             }
12729             break;
12730           default:
12731             break;
12732         }
12733     }
12734 }
12735
12736
12737 void
12738 StopObservingEvent()
12739 {
12740     /* Stop observing current games */
12741     SendToICS(ics_prefix);
12742     SendToICS("unobserve\n");
12743 }
12744
12745 void
12746 StopExaminingEvent()
12747 {
12748     /* Stop observing current game */
12749     SendToICS(ics_prefix);
12750     SendToICS("unexamine\n");
12751 }
12752
12753 void
12754 ForwardInner(target)
12755      int target;
12756 {
12757     int limit;
12758
12759     if (appData.debugMode)
12760         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12761                 target, currentMove, forwardMostMove);
12762
12763     if (gameMode == EditPosition)
12764       return;
12765
12766     if (gameMode == PlayFromGameFile && !pausing)
12767       PauseEvent();
12768
12769     if (gameMode == IcsExamining && pausing)
12770       limit = pauseExamForwardMostMove;
12771     else
12772       limit = forwardMostMove;
12773
12774     if (target > limit) target = limit;
12775
12776     if (target > 0 && moveList[target - 1][0]) {
12777         int fromX, fromY, toX, toY;
12778         toX = moveList[target - 1][2] - AAA;
12779         toY = moveList[target - 1][3] - ONE;
12780         if (moveList[target - 1][1] == '@') {
12781             if (appData.highlightLastMove) {
12782                 SetHighlights(-1, -1, toX, toY);
12783             }
12784         } else {
12785             fromX = moveList[target - 1][0] - AAA;
12786             fromY = moveList[target - 1][1] - ONE;
12787             if (target == currentMove + 1) {
12788                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12789             }
12790             if (appData.highlightLastMove) {
12791                 SetHighlights(fromX, fromY, toX, toY);
12792             }
12793         }
12794     }
12795     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12796         gameMode == Training || gameMode == PlayFromGameFile ||
12797         gameMode == AnalyzeFile) {
12798         while (currentMove < target) {
12799             SendMoveToProgram(currentMove++, &first);
12800         }
12801     } else {
12802         currentMove = target;
12803     }
12804
12805     if (gameMode == EditGame || gameMode == EndOfGame) {
12806         whiteTimeRemaining = timeRemaining[0][currentMove];
12807         blackTimeRemaining = timeRemaining[1][currentMove];
12808     }
12809     DisplayBothClocks();
12810     DisplayMove(currentMove - 1);
12811     DrawPosition(FALSE, boards[currentMove]);
12812     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12813     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12814         DisplayComment(currentMove - 1, commentList[currentMove]);
12815     }
12816 }
12817
12818
12819 void
12820 ForwardEvent()
12821 {
12822     if (gameMode == IcsExamining && !pausing) {
12823         SendToICS(ics_prefix);
12824         SendToICS("forward\n");
12825     } else {
12826         ForwardInner(currentMove + 1);
12827     }
12828 }
12829
12830 void
12831 ToEndEvent()
12832 {
12833     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12834         /* to optimze, we temporarily turn off analysis mode while we feed
12835          * the remaining moves to the engine. Otherwise we get analysis output
12836          * after each move.
12837          */
12838         if (first.analysisSupport) {
12839           SendToProgram("exit\nforce\n", &first);
12840           first.analyzing = FALSE;
12841         }
12842     }
12843
12844     if (gameMode == IcsExamining && !pausing) {
12845         SendToICS(ics_prefix);
12846         SendToICS("forward 999999\n");
12847     } else {
12848         ForwardInner(forwardMostMove);
12849     }
12850
12851     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12852         /* we have fed all the moves, so reactivate analysis mode */
12853         SendToProgram("analyze\n", &first);
12854         first.analyzing = TRUE;
12855         /*first.maybeThinking = TRUE;*/
12856         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12857     }
12858 }
12859
12860 void
12861 BackwardInner(target)
12862      int target;
12863 {
12864     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12865
12866     if (appData.debugMode)
12867         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12868                 target, currentMove, forwardMostMove);
12869
12870     if (gameMode == EditPosition) return;
12871     if (currentMove <= backwardMostMove) {
12872         ClearHighlights();
12873         DrawPosition(full_redraw, boards[currentMove]);
12874         return;
12875     }
12876     if (gameMode == PlayFromGameFile && !pausing)
12877       PauseEvent();
12878
12879     if (moveList[target][0]) {
12880         int fromX, fromY, toX, toY;
12881         toX = moveList[target][2] - AAA;
12882         toY = moveList[target][3] - ONE;
12883         if (moveList[target][1] == '@') {
12884             if (appData.highlightLastMove) {
12885                 SetHighlights(-1, -1, toX, toY);
12886             }
12887         } else {
12888             fromX = moveList[target][0] - AAA;
12889             fromY = moveList[target][1] - ONE;
12890             if (target == currentMove - 1) {
12891                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12892             }
12893             if (appData.highlightLastMove) {
12894                 SetHighlights(fromX, fromY, toX, toY);
12895             }
12896         }
12897     }
12898     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12899         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12900         while (currentMove > target) {
12901             SendToProgram("undo\n", &first);
12902             currentMove--;
12903         }
12904     } else {
12905         currentMove = target;
12906     }
12907
12908     if (gameMode == EditGame || gameMode == EndOfGame) {
12909         whiteTimeRemaining = timeRemaining[0][currentMove];
12910         blackTimeRemaining = timeRemaining[1][currentMove];
12911     }
12912     DisplayBothClocks();
12913     DisplayMove(currentMove - 1);
12914     DrawPosition(full_redraw, boards[currentMove]);
12915     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12916     // [HGM] PV info: routine tests if comment empty
12917     DisplayComment(currentMove - 1, commentList[currentMove]);
12918 }
12919
12920 void
12921 BackwardEvent()
12922 {
12923     if (gameMode == IcsExamining && !pausing) {
12924         SendToICS(ics_prefix);
12925         SendToICS("backward\n");
12926     } else {
12927         BackwardInner(currentMove - 1);
12928     }
12929 }
12930
12931 void
12932 ToStartEvent()
12933 {
12934     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12935         /* to optimize, we temporarily turn off analysis mode while we undo
12936          * all the moves. Otherwise we get analysis output after each undo.
12937          */
12938         if (first.analysisSupport) {
12939           SendToProgram("exit\nforce\n", &first);
12940           first.analyzing = FALSE;
12941         }
12942     }
12943
12944     if (gameMode == IcsExamining && !pausing) {
12945         SendToICS(ics_prefix);
12946         SendToICS("backward 999999\n");
12947     } else {
12948         BackwardInner(backwardMostMove);
12949     }
12950
12951     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12952         /* we have fed all the moves, so reactivate analysis mode */
12953         SendToProgram("analyze\n", &first);
12954         first.analyzing = TRUE;
12955         /*first.maybeThinking = TRUE;*/
12956         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12957     }
12958 }
12959
12960 void
12961 ToNrEvent(int to)
12962 {
12963   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12964   if (to >= forwardMostMove) to = forwardMostMove;
12965   if (to <= backwardMostMove) to = backwardMostMove;
12966   if (to < currentMove) {
12967     BackwardInner(to);
12968   } else {
12969     ForwardInner(to);
12970   }
12971 }
12972
12973 void
12974 RevertEvent(Boolean annotate)
12975 {
12976     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12977         return;
12978     }
12979     if (gameMode != IcsExamining) {
12980         DisplayError(_("You are not examining a game"), 0);
12981         return;
12982     }
12983     if (pausing) {
12984         DisplayError(_("You can't revert while pausing"), 0);
12985         return;
12986     }
12987     SendToICS(ics_prefix);
12988     SendToICS("revert\n");
12989 }
12990
12991 void
12992 RetractMoveEvent()
12993 {
12994     switch (gameMode) {
12995       case MachinePlaysWhite:
12996       case MachinePlaysBlack:
12997         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12998             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12999             return;
13000         }
13001         if (forwardMostMove < 2) return;
13002         currentMove = forwardMostMove = forwardMostMove - 2;
13003         whiteTimeRemaining = timeRemaining[0][currentMove];
13004         blackTimeRemaining = timeRemaining[1][currentMove];
13005         DisplayBothClocks();
13006         DisplayMove(currentMove - 1);
13007         ClearHighlights();/*!! could figure this out*/
13008         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13009         SendToProgram("remove\n", &first);
13010         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13011         break;
13012
13013       case BeginningOfGame:
13014       default:
13015         break;
13016
13017       case IcsPlayingWhite:
13018       case IcsPlayingBlack:
13019         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13020             SendToICS(ics_prefix);
13021             SendToICS("takeback 2\n");
13022         } else {
13023             SendToICS(ics_prefix);
13024             SendToICS("takeback 1\n");
13025         }
13026         break;
13027     }
13028 }
13029
13030 void
13031 MoveNowEvent()
13032 {
13033     ChessProgramState *cps;
13034
13035     switch (gameMode) {
13036       case MachinePlaysWhite:
13037         if (!WhiteOnMove(forwardMostMove)) {
13038             DisplayError(_("It is your turn"), 0);
13039             return;
13040         }
13041         cps = &first;
13042         break;
13043       case MachinePlaysBlack:
13044         if (WhiteOnMove(forwardMostMove)) {
13045             DisplayError(_("It is your turn"), 0);
13046             return;
13047         }
13048         cps = &first;
13049         break;
13050       case TwoMachinesPlay:
13051         if (WhiteOnMove(forwardMostMove) ==
13052             (first.twoMachinesColor[0] == 'w')) {
13053             cps = &first;
13054         } else {
13055             cps = &second;
13056         }
13057         break;
13058       case BeginningOfGame:
13059       default:
13060         return;
13061     }
13062     SendToProgram("?\n", cps);
13063 }
13064
13065 void
13066 TruncateGameEvent()
13067 {
13068     EditGameEvent();
13069     if (gameMode != EditGame) return;
13070     TruncateGame();
13071 }
13072
13073 void
13074 TruncateGame()
13075 {
13076     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13077     if (forwardMostMove > currentMove) {
13078         if (gameInfo.resultDetails != NULL) {
13079             free(gameInfo.resultDetails);
13080             gameInfo.resultDetails = NULL;
13081             gameInfo.result = GameUnfinished;
13082         }
13083         forwardMostMove = currentMove;
13084         HistorySet(parseList, backwardMostMove, forwardMostMove,
13085                    currentMove-1);
13086     }
13087 }
13088
13089 void
13090 HintEvent()
13091 {
13092     if (appData.noChessProgram) return;
13093     switch (gameMode) {
13094       case MachinePlaysWhite:
13095         if (WhiteOnMove(forwardMostMove)) {
13096             DisplayError(_("Wait until your turn"), 0);
13097             return;
13098         }
13099         break;
13100       case BeginningOfGame:
13101       case MachinePlaysBlack:
13102         if (!WhiteOnMove(forwardMostMove)) {
13103             DisplayError(_("Wait until your turn"), 0);
13104             return;
13105         }
13106         break;
13107       default:
13108         DisplayError(_("No hint available"), 0);
13109         return;
13110     }
13111     SendToProgram("hint\n", &first);
13112     hintRequested = TRUE;
13113 }
13114
13115 void
13116 BookEvent()
13117 {
13118     if (appData.noChessProgram) return;
13119     switch (gameMode) {
13120       case MachinePlaysWhite:
13121         if (WhiteOnMove(forwardMostMove)) {
13122             DisplayError(_("Wait until your turn"), 0);
13123             return;
13124         }
13125         break;
13126       case BeginningOfGame:
13127       case MachinePlaysBlack:
13128         if (!WhiteOnMove(forwardMostMove)) {
13129             DisplayError(_("Wait until your turn"), 0);
13130             return;
13131         }
13132         break;
13133       case EditPosition:
13134         EditPositionDone(TRUE);
13135         break;
13136       case TwoMachinesPlay:
13137         return;
13138       default:
13139         break;
13140     }
13141     SendToProgram("bk\n", &first);
13142     bookOutput[0] = NULLCHAR;
13143     bookRequested = TRUE;
13144 }
13145
13146 void
13147 AboutGameEvent()
13148 {
13149     char *tags = PGNTags(&gameInfo);
13150     TagsPopUp(tags, CmailMsg());
13151     free(tags);
13152 }
13153
13154 /* end button procedures */
13155
13156 void
13157 PrintPosition(fp, move)
13158      FILE *fp;
13159      int move;
13160 {
13161     int i, j;
13162
13163     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13164         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13165             char c = PieceToChar(boards[move][i][j]);
13166             fputc(c == 'x' ? '.' : c, fp);
13167             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13168         }
13169     }
13170     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13171       fprintf(fp, "white to play\n");
13172     else
13173       fprintf(fp, "black to play\n");
13174 }
13175
13176 void
13177 PrintOpponents(fp)
13178      FILE *fp;
13179 {
13180     if (gameInfo.white != NULL) {
13181         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13182     } else {
13183         fprintf(fp, "\n");
13184     }
13185 }
13186
13187 /* Find last component of program's own name, using some heuristics */
13188 void
13189 TidyProgramName(prog, host, buf)
13190      char *prog, *host, buf[MSG_SIZ];
13191 {
13192     char *p, *q;
13193     int local = (strcmp(host, "localhost") == 0);
13194     while (!local && (p = strchr(prog, ';')) != NULL) {
13195         p++;
13196         while (*p == ' ') p++;
13197         prog = p;
13198     }
13199     if (*prog == '"' || *prog == '\'') {
13200         q = strchr(prog + 1, *prog);
13201     } else {
13202         q = strchr(prog, ' ');
13203     }
13204     if (q == NULL) q = prog + strlen(prog);
13205     p = q;
13206     while (p >= prog && *p != '/' && *p != '\\') p--;
13207     p++;
13208     if(p == prog && *p == '"') p++;
13209     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13210     memcpy(buf, p, q - p);
13211     buf[q - p] = NULLCHAR;
13212     if (!local) {
13213         strcat(buf, "@");
13214         strcat(buf, host);
13215     }
13216 }
13217
13218 char *
13219 TimeControlTagValue()
13220 {
13221     char buf[MSG_SIZ];
13222     if (!appData.clockMode) {
13223       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13224     } else if (movesPerSession > 0) {
13225       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13226     } else if (timeIncrement == 0) {
13227       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13228     } else {
13229       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13230     }
13231     return StrSave(buf);
13232 }
13233
13234 void
13235 SetGameInfo()
13236 {
13237     /* This routine is used only for certain modes */
13238     VariantClass v = gameInfo.variant;
13239     ChessMove r = GameUnfinished;
13240     char *p = NULL;
13241
13242     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13243         r = gameInfo.result;
13244         p = gameInfo.resultDetails;
13245         gameInfo.resultDetails = NULL;
13246     }
13247     ClearGameInfo(&gameInfo);
13248     gameInfo.variant = v;
13249
13250     switch (gameMode) {
13251       case MachinePlaysWhite:
13252         gameInfo.event = StrSave( appData.pgnEventHeader );
13253         gameInfo.site = StrSave(HostName());
13254         gameInfo.date = PGNDate();
13255         gameInfo.round = StrSave("-");
13256         gameInfo.white = StrSave(first.tidy);
13257         gameInfo.black = StrSave(UserName());
13258         gameInfo.timeControl = TimeControlTagValue();
13259         break;
13260
13261       case MachinePlaysBlack:
13262         gameInfo.event = StrSave( appData.pgnEventHeader );
13263         gameInfo.site = StrSave(HostName());
13264         gameInfo.date = PGNDate();
13265         gameInfo.round = StrSave("-");
13266         gameInfo.white = StrSave(UserName());
13267         gameInfo.black = StrSave(first.tidy);
13268         gameInfo.timeControl = TimeControlTagValue();
13269         break;
13270
13271       case TwoMachinesPlay:
13272         gameInfo.event = StrSave( appData.pgnEventHeader );
13273         gameInfo.site = StrSave(HostName());
13274         gameInfo.date = PGNDate();
13275         if (matchGame > 0) {
13276             char buf[MSG_SIZ];
13277             snprintf(buf, MSG_SIZ, "%d", matchGame);
13278             gameInfo.round = StrSave(buf);
13279         } else {
13280             gameInfo.round = StrSave("-");
13281         }
13282         if (first.twoMachinesColor[0] == 'w') {
13283             gameInfo.white = StrSave(first.tidy);
13284             gameInfo.black = StrSave(second.tidy);
13285         } else {
13286             gameInfo.white = StrSave(second.tidy);
13287             gameInfo.black = StrSave(first.tidy);
13288         }
13289         gameInfo.timeControl = TimeControlTagValue();
13290         break;
13291
13292       case EditGame:
13293         gameInfo.event = StrSave("Edited game");
13294         gameInfo.site = StrSave(HostName());
13295         gameInfo.date = PGNDate();
13296         gameInfo.round = StrSave("-");
13297         gameInfo.white = StrSave("-");
13298         gameInfo.black = StrSave("-");
13299         gameInfo.result = r;
13300         gameInfo.resultDetails = p;
13301         break;
13302
13303       case EditPosition:
13304         gameInfo.event = StrSave("Edited position");
13305         gameInfo.site = StrSave(HostName());
13306         gameInfo.date = PGNDate();
13307         gameInfo.round = StrSave("-");
13308         gameInfo.white = StrSave("-");
13309         gameInfo.black = StrSave("-");
13310         break;
13311
13312       case IcsPlayingWhite:
13313       case IcsPlayingBlack:
13314       case IcsObserving:
13315       case IcsExamining:
13316         break;
13317
13318       case PlayFromGameFile:
13319         gameInfo.event = StrSave("Game from non-PGN file");
13320         gameInfo.site = StrSave(HostName());
13321         gameInfo.date = PGNDate();
13322         gameInfo.round = StrSave("-");
13323         gameInfo.white = StrSave("?");
13324         gameInfo.black = StrSave("?");
13325         break;
13326
13327       default:
13328         break;
13329     }
13330 }
13331
13332 void
13333 ReplaceComment(index, text)
13334      int index;
13335      char *text;
13336 {
13337     int len;
13338     char *p;
13339     float score;
13340
13341     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13342        pvInfoList[index-1].depth == len &&
13343        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13344        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13345     while (*text == '\n') text++;
13346     len = strlen(text);
13347     while (len > 0 && text[len - 1] == '\n') len--;
13348
13349     if (commentList[index] != NULL)
13350       free(commentList[index]);
13351
13352     if (len == 0) {
13353         commentList[index] = NULL;
13354         return;
13355     }
13356   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13357       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13358       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13359     commentList[index] = (char *) malloc(len + 2);
13360     strncpy(commentList[index], text, len);
13361     commentList[index][len] = '\n';
13362     commentList[index][len + 1] = NULLCHAR;
13363   } else {
13364     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13365     char *p;
13366     commentList[index] = (char *) malloc(len + 7);
13367     safeStrCpy(commentList[index], "{\n", 3);
13368     safeStrCpy(commentList[index]+2, text, len+1);
13369     commentList[index][len+2] = NULLCHAR;
13370     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13371     strcat(commentList[index], "\n}\n");
13372   }
13373 }
13374
13375 void
13376 CrushCRs(text)
13377      char *text;
13378 {
13379   char *p = text;
13380   char *q = text;
13381   char ch;
13382
13383   do {
13384     ch = *p++;
13385     if (ch == '\r') continue;
13386     *q++ = ch;
13387   } while (ch != '\0');
13388 }
13389
13390 void
13391 AppendComment(index, text, addBraces)
13392      int index;
13393      char *text;
13394      Boolean addBraces; // [HGM] braces: tells if we should add {}
13395 {
13396     int oldlen, len;
13397     char *old;
13398
13399 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13400     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13401
13402     CrushCRs(text);
13403     while (*text == '\n') text++;
13404     len = strlen(text);
13405     while (len > 0 && text[len - 1] == '\n') len--;
13406
13407     if (len == 0) return;
13408
13409     if (commentList[index] != NULL) {
13410         old = commentList[index];
13411         oldlen = strlen(old);
13412         while(commentList[index][oldlen-1] ==  '\n')
13413           commentList[index][--oldlen] = NULLCHAR;
13414         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13415         safeStrCpy(commentList[index], old, oldlen + len + 6);
13416         free(old);
13417         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13418         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13419           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13420           while (*text == '\n') { text++; len--; }
13421           commentList[index][--oldlen] = NULLCHAR;
13422       }
13423         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13424         else          strcat(commentList[index], "\n");
13425         strcat(commentList[index], text);
13426         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13427         else          strcat(commentList[index], "\n");
13428     } else {
13429         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13430         if(addBraces)
13431           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13432         else commentList[index][0] = NULLCHAR;
13433         strcat(commentList[index], text);
13434         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13435         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13436     }
13437 }
13438
13439 static char * FindStr( char * text, char * sub_text )
13440 {
13441     char * result = strstr( text, sub_text );
13442
13443     if( result != NULL ) {
13444         result += strlen( sub_text );
13445     }
13446
13447     return result;
13448 }
13449
13450 /* [AS] Try to extract PV info from PGN comment */
13451 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13452 char *GetInfoFromComment( int index, char * text )
13453 {
13454     char * sep = text, *p;
13455
13456     if( text != NULL && index > 0 ) {
13457         int score = 0;
13458         int depth = 0;
13459         int time = -1, sec = 0, deci;
13460         char * s_eval = FindStr( text, "[%eval " );
13461         char * s_emt = FindStr( text, "[%emt " );
13462
13463         if( s_eval != NULL || s_emt != NULL ) {
13464             /* New style */
13465             char delim;
13466
13467             if( s_eval != NULL ) {
13468                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13469                     return text;
13470                 }
13471
13472                 if( delim != ']' ) {
13473                     return text;
13474                 }
13475             }
13476
13477             if( s_emt != NULL ) {
13478             }
13479                 return text;
13480         }
13481         else {
13482             /* We expect something like: [+|-]nnn.nn/dd */
13483             int score_lo = 0;
13484
13485             if(*text != '{') return text; // [HGM] braces: must be normal comment
13486
13487             sep = strchr( text, '/' );
13488             if( sep == NULL || sep < (text+4) ) {
13489                 return text;
13490             }
13491
13492             p = text;
13493             if(p[1] == '(') { // comment starts with PV
13494                p = strchr(p, ')'); // locate end of PV
13495                if(p == NULL || sep < p+5) return text;
13496                // at this point we have something like "{(.*) +0.23/6 ..."
13497                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13498                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13499                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13500             }
13501             time = -1; sec = -1; deci = -1;
13502             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13503                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13504                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13505                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13506                 return text;
13507             }
13508
13509             if( score_lo < 0 || score_lo >= 100 ) {
13510                 return text;
13511             }
13512
13513             if(sec >= 0) time = 600*time + 10*sec; else
13514             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13515
13516             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13517
13518             /* [HGM] PV time: now locate end of PV info */
13519             while( *++sep >= '0' && *sep <= '9'); // strip depth
13520             if(time >= 0)
13521             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13522             if(sec >= 0)
13523             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13524             if(deci >= 0)
13525             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13526             while(*sep == ' ') sep++;
13527         }
13528
13529         if( depth <= 0 ) {
13530             return text;
13531         }
13532
13533         if( time < 0 ) {
13534             time = -1;
13535         }
13536
13537         pvInfoList[index-1].depth = depth;
13538         pvInfoList[index-1].score = score;
13539         pvInfoList[index-1].time  = 10*time; // centi-sec
13540         if(*sep == '}') *sep = 0; else *--sep = '{';
13541         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13542     }
13543     return sep;
13544 }
13545
13546 void
13547 SendToProgram(message, cps)
13548      char *message;
13549      ChessProgramState *cps;
13550 {
13551     int count, outCount, error;
13552     char buf[MSG_SIZ];
13553
13554     if (cps->pr == NULL) return;
13555     Attention(cps);
13556
13557     if (appData.debugMode) {
13558         TimeMark now;
13559         GetTimeMark(&now);
13560         fprintf(debugFP, "%ld >%-6s: %s",
13561                 SubtractTimeMarks(&now, &programStartTime),
13562                 cps->which, message);
13563     }
13564
13565     count = strlen(message);
13566     outCount = OutputToProcess(cps->pr, message, count, &error);
13567     if (outCount < count && !exiting
13568                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13569       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13570         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13571             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13572                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13573                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13574             } else {
13575                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13576             }
13577             gameInfo.resultDetails = StrSave(buf);
13578         }
13579         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13580     }
13581 }
13582
13583 void
13584 ReceiveFromProgram(isr, closure, message, count, error)
13585      InputSourceRef isr;
13586      VOIDSTAR closure;
13587      char *message;
13588      int count;
13589      int error;
13590 {
13591     char *end_str;
13592     char buf[MSG_SIZ];
13593     ChessProgramState *cps = (ChessProgramState *)closure;
13594
13595     if (isr != cps->isr) return; /* Killed intentionally */
13596     if (count <= 0) {
13597         if (count == 0) {
13598             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13599                     cps->which, cps->program);
13600         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13601                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13602                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13603                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13604                 } else {
13605                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13606                 }
13607                 gameInfo.resultDetails = StrSave(buf);
13608             }
13609             RemoveInputSource(cps->isr);
13610             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13611         } else {
13612             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13613                     cps->which, cps->program);
13614             RemoveInputSource(cps->isr);
13615
13616             /* [AS] Program is misbehaving badly... kill it */
13617             if( count == -2 ) {
13618                 DestroyChildProcess( cps->pr, 9 );
13619                 cps->pr = NoProc;
13620             }
13621
13622             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13623         }
13624         return;
13625     }
13626
13627     if ((end_str = strchr(message, '\r')) != NULL)
13628       *end_str = NULLCHAR;
13629     if ((end_str = strchr(message, '\n')) != NULL)
13630       *end_str = NULLCHAR;
13631
13632     if (appData.debugMode) {
13633         TimeMark now; int print = 1;
13634         char *quote = ""; char c; int i;
13635
13636         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13637                 char start = message[0];
13638                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13639                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13640                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13641                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13642                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13643                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13644                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13645                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13646                    sscanf(message, "hint: %c", &c)!=1 && 
13647                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13648                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13649                     print = (appData.engineComments >= 2);
13650                 }
13651                 message[0] = start; // restore original message
13652         }
13653         if(print) {
13654                 GetTimeMark(&now);
13655                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13656                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13657                         quote,
13658                         message);
13659         }
13660     }
13661
13662     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13663     if (appData.icsEngineAnalyze) {
13664         if (strstr(message, "whisper") != NULL ||
13665              strstr(message, "kibitz") != NULL ||
13666             strstr(message, "tellics") != NULL) return;
13667     }
13668
13669     HandleMachineMove(message, cps);
13670 }
13671
13672
13673 void
13674 SendTimeControl(cps, mps, tc, inc, sd, st)
13675      ChessProgramState *cps;
13676      int mps, inc, sd, st;
13677      long tc;
13678 {
13679     char buf[MSG_SIZ];
13680     int seconds;
13681
13682     if( timeControl_2 > 0 ) {
13683         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13684             tc = timeControl_2;
13685         }
13686     }
13687     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13688     inc /= cps->timeOdds;
13689     st  /= cps->timeOdds;
13690
13691     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13692
13693     if (st > 0) {
13694       /* Set exact time per move, normally using st command */
13695       if (cps->stKludge) {
13696         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13697         seconds = st % 60;
13698         if (seconds == 0) {
13699           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13700         } else {
13701           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13702         }
13703       } else {
13704         snprintf(buf, MSG_SIZ, "st %d\n", st);
13705       }
13706     } else {
13707       /* Set conventional or incremental time control, using level command */
13708       if (seconds == 0) {
13709         /* Note old gnuchess bug -- minutes:seconds used to not work.
13710            Fixed in later versions, but still avoid :seconds
13711            when seconds is 0. */
13712         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13713       } else {
13714         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13715                  seconds, inc/1000.);
13716       }
13717     }
13718     SendToProgram(buf, cps);
13719
13720     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13721     /* Orthogonally, limit search to given depth */
13722     if (sd > 0) {
13723       if (cps->sdKludge) {
13724         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13725       } else {
13726         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13727       }
13728       SendToProgram(buf, cps);
13729     }
13730
13731     if(cps->nps > 0) { /* [HGM] nps */
13732         if(cps->supportsNPS == FALSE)
13733           cps->nps = -1; // don't use if engine explicitly says not supported!
13734         else {
13735           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13736           SendToProgram(buf, cps);
13737         }
13738     }
13739 }
13740
13741 ChessProgramState *WhitePlayer()
13742 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13743 {
13744     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13745        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13746         return &second;
13747     return &first;
13748 }
13749
13750 void
13751 SendTimeRemaining(cps, machineWhite)
13752      ChessProgramState *cps;
13753      int /*boolean*/ machineWhite;
13754 {
13755     char message[MSG_SIZ];
13756     long time, otime;
13757
13758     /* Note: this routine must be called when the clocks are stopped
13759        or when they have *just* been set or switched; otherwise
13760        it will be off by the time since the current tick started.
13761     */
13762     if (machineWhite) {
13763         time = whiteTimeRemaining / 10;
13764         otime = blackTimeRemaining / 10;
13765     } else {
13766         time = blackTimeRemaining / 10;
13767         otime = whiteTimeRemaining / 10;
13768     }
13769     /* [HGM] translate opponent's time by time-odds factor */
13770     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13771     if (appData.debugMode) {
13772         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13773     }
13774
13775     if (time <= 0) time = 1;
13776     if (otime <= 0) otime = 1;
13777
13778     snprintf(message, MSG_SIZ, "time %ld\n", time);
13779     SendToProgram(message, cps);
13780
13781     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13782     SendToProgram(message, cps);
13783 }
13784
13785 int
13786 BoolFeature(p, name, loc, cps)
13787      char **p;
13788      char *name;
13789      int *loc;
13790      ChessProgramState *cps;
13791 {
13792   char buf[MSG_SIZ];
13793   int len = strlen(name);
13794   int val;
13795
13796   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13797     (*p) += len + 1;
13798     sscanf(*p, "%d", &val);
13799     *loc = (val != 0);
13800     while (**p && **p != ' ')
13801       (*p)++;
13802     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13803     SendToProgram(buf, cps);
13804     return TRUE;
13805   }
13806   return FALSE;
13807 }
13808
13809 int
13810 IntFeature(p, name, loc, cps)
13811      char **p;
13812      char *name;
13813      int *loc;
13814      ChessProgramState *cps;
13815 {
13816   char buf[MSG_SIZ];
13817   int len = strlen(name);
13818   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13819     (*p) += len + 1;
13820     sscanf(*p, "%d", loc);
13821     while (**p && **p != ' ') (*p)++;
13822     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13823     SendToProgram(buf, cps);
13824     return TRUE;
13825   }
13826   return FALSE;
13827 }
13828
13829 int
13830 StringFeature(p, name, loc, cps)
13831      char **p;
13832      char *name;
13833      char loc[];
13834      ChessProgramState *cps;
13835 {
13836   char buf[MSG_SIZ];
13837   int len = strlen(name);
13838   if (strncmp((*p), name, len) == 0
13839       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13840     (*p) += len + 2;
13841     sscanf(*p, "%[^\"]", loc);
13842     while (**p && **p != '\"') (*p)++;
13843     if (**p == '\"') (*p)++;
13844     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13845     SendToProgram(buf, cps);
13846     return TRUE;
13847   }
13848   return FALSE;
13849 }
13850
13851 int
13852 ParseOption(Option *opt, ChessProgramState *cps)
13853 // [HGM] options: process the string that defines an engine option, and determine
13854 // name, type, default value, and allowed value range
13855 {
13856         char *p, *q, buf[MSG_SIZ];
13857         int n, min = (-1)<<31, max = 1<<31, def;
13858
13859         if(p = strstr(opt->name, " -spin ")) {
13860             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13861             if(max < min) max = min; // enforce consistency
13862             if(def < min) def = min;
13863             if(def > max) def = max;
13864             opt->value = def;
13865             opt->min = min;
13866             opt->max = max;
13867             opt->type = Spin;
13868         } else if((p = strstr(opt->name, " -slider "))) {
13869             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13870             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13871             if(max < min) max = min; // enforce consistency
13872             if(def < min) def = min;
13873             if(def > max) def = max;
13874             opt->value = def;
13875             opt->min = min;
13876             opt->max = max;
13877             opt->type = Spin; // Slider;
13878         } else if((p = strstr(opt->name, " -string "))) {
13879             opt->textValue = p+9;
13880             opt->type = TextBox;
13881         } else if((p = strstr(opt->name, " -file "))) {
13882             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13883             opt->textValue = p+7;
13884             opt->type = TextBox; // FileName;
13885         } else if((p = strstr(opt->name, " -path "))) {
13886             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13887             opt->textValue = p+7;
13888             opt->type = TextBox; // PathName;
13889         } else if(p = strstr(opt->name, " -check ")) {
13890             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13891             opt->value = (def != 0);
13892             opt->type = CheckBox;
13893         } else if(p = strstr(opt->name, " -combo ")) {
13894             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13895             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13896             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13897             opt->value = n = 0;
13898             while(q = StrStr(q, " /// ")) {
13899                 n++; *q = 0;    // count choices, and null-terminate each of them
13900                 q += 5;
13901                 if(*q == '*') { // remember default, which is marked with * prefix
13902                     q++;
13903                     opt->value = n;
13904                 }
13905                 cps->comboList[cps->comboCnt++] = q;
13906             }
13907             cps->comboList[cps->comboCnt++] = NULL;
13908             opt->max = n + 1;
13909             opt->type = ComboBox;
13910         } else if(p = strstr(opt->name, " -button")) {
13911             opt->type = Button;
13912         } else if(p = strstr(opt->name, " -save")) {
13913             opt->type = SaveButton;
13914         } else return FALSE;
13915         *p = 0; // terminate option name
13916         // now look if the command-line options define a setting for this engine option.
13917         if(cps->optionSettings && cps->optionSettings[0])
13918             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13919         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13920           snprintf(buf, MSG_SIZ, "option %s", p);
13921                 if(p = strstr(buf, ",")) *p = 0;
13922                 if(q = strchr(buf, '=')) switch(opt->type) {
13923                     case ComboBox:
13924                         for(n=0; n<opt->max; n++)
13925                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13926                         break;
13927                     case TextBox:
13928                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13929                         break;
13930                     case Spin:
13931                     case CheckBox:
13932                         opt->value = atoi(q+1);
13933                     default:
13934                         break;
13935                 }
13936                 strcat(buf, "\n");
13937                 SendToProgram(buf, cps);
13938         }
13939         return TRUE;
13940 }
13941
13942 void
13943 FeatureDone(cps, val)
13944      ChessProgramState* cps;
13945      int val;
13946 {
13947   DelayedEventCallback cb = GetDelayedEvent();
13948   if ((cb == InitBackEnd3 && cps == &first) ||
13949       (cb == SettingsMenuIfReady && cps == &second) ||
13950       (cb == TwoMachinesEventIfReady && cps == &second)) {
13951     CancelDelayedEvent();
13952     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13953   }
13954   cps->initDone = val;
13955 }
13956
13957 /* Parse feature command from engine */
13958 void
13959 ParseFeatures(args, cps)
13960      char* args;
13961      ChessProgramState *cps;
13962 {
13963   char *p = args;
13964   char *q;
13965   int val;
13966   char buf[MSG_SIZ];
13967
13968   for (;;) {
13969     while (*p == ' ') p++;
13970     if (*p == NULLCHAR) return;
13971
13972     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13973     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13974     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13975     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13976     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13977     if (BoolFeature(&p, "reuse", &val, cps)) {
13978       /* Engine can disable reuse, but can't enable it if user said no */
13979       if (!val) cps->reuse = FALSE;
13980       continue;
13981     }
13982     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13983     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13984       if (gameMode == TwoMachinesPlay) {
13985         DisplayTwoMachinesTitle();
13986       } else {
13987         DisplayTitle("");
13988       }
13989       continue;
13990     }
13991     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13992     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13993     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13994     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13995     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13996     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13997     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13998     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13999     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14000     if (IntFeature(&p, "done", &val, cps)) {
14001       FeatureDone(cps, val);
14002       continue;
14003     }
14004     /* Added by Tord: */
14005     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14006     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14007     /* End of additions by Tord */
14008
14009     /* [HGM] added features: */
14010     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14011     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14012     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14013     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14014     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14015     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14016     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14017         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14018           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14019             SendToProgram(buf, cps);
14020             continue;
14021         }
14022         if(cps->nrOptions >= MAX_OPTIONS) {
14023             cps->nrOptions--;
14024             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
14025             DisplayError(buf, 0);
14026         }
14027         continue;
14028     }
14029     /* End of additions by HGM */
14030
14031     /* unknown feature: complain and skip */
14032     q = p;
14033     while (*q && *q != '=') q++;
14034     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14035     SendToProgram(buf, cps);
14036     p = q;
14037     if (*p == '=') {
14038       p++;
14039       if (*p == '\"') {
14040         p++;
14041         while (*p && *p != '\"') p++;
14042         if (*p == '\"') p++;
14043       } else {
14044         while (*p && *p != ' ') p++;
14045       }
14046     }
14047   }
14048
14049 }
14050
14051 void
14052 PeriodicUpdatesEvent(newState)
14053      int newState;
14054 {
14055     if (newState == appData.periodicUpdates)
14056       return;
14057
14058     appData.periodicUpdates=newState;
14059
14060     /* Display type changes, so update it now */
14061 //    DisplayAnalysis();
14062
14063     /* Get the ball rolling again... */
14064     if (newState) {
14065         AnalysisPeriodicEvent(1);
14066         StartAnalysisClock();
14067     }
14068 }
14069
14070 void
14071 PonderNextMoveEvent(newState)
14072      int newState;
14073 {
14074     if (newState == appData.ponderNextMove) return;
14075     if (gameMode == EditPosition) EditPositionDone(TRUE);
14076     if (newState) {
14077         SendToProgram("hard\n", &first);
14078         if (gameMode == TwoMachinesPlay) {
14079             SendToProgram("hard\n", &second);
14080         }
14081     } else {
14082         SendToProgram("easy\n", &first);
14083         thinkOutput[0] = NULLCHAR;
14084         if (gameMode == TwoMachinesPlay) {
14085             SendToProgram("easy\n", &second);
14086         }
14087     }
14088     appData.ponderNextMove = newState;
14089 }
14090
14091 void
14092 NewSettingEvent(option, feature, command, value)
14093      char *command;
14094      int option, value, *feature;
14095 {
14096     char buf[MSG_SIZ];
14097
14098     if (gameMode == EditPosition) EditPositionDone(TRUE);
14099     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14100     if(feature == NULL || *feature) SendToProgram(buf, &first);
14101     if (gameMode == TwoMachinesPlay) {
14102         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14103     }
14104 }
14105
14106 void
14107 ShowThinkingEvent()
14108 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14109 {
14110     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14111     int newState = appData.showThinking
14112         // [HGM] thinking: other features now need thinking output as well
14113         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14114
14115     if (oldState == newState) return;
14116     oldState = newState;
14117     if (gameMode == EditPosition) EditPositionDone(TRUE);
14118     if (oldState) {
14119         SendToProgram("post\n", &first);
14120         if (gameMode == TwoMachinesPlay) {
14121             SendToProgram("post\n", &second);
14122         }
14123     } else {
14124         SendToProgram("nopost\n", &first);
14125         thinkOutput[0] = NULLCHAR;
14126         if (gameMode == TwoMachinesPlay) {
14127             SendToProgram("nopost\n", &second);
14128         }
14129     }
14130 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14131 }
14132
14133 void
14134 AskQuestionEvent(title, question, replyPrefix, which)
14135      char *title; char *question; char *replyPrefix; char *which;
14136 {
14137   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14138   if (pr == NoProc) return;
14139   AskQuestion(title, question, replyPrefix, pr);
14140 }
14141
14142 void
14143 DisplayMove(moveNumber)
14144      int moveNumber;
14145 {
14146     char message[MSG_SIZ];
14147     char res[MSG_SIZ];
14148     char cpThinkOutput[MSG_SIZ];
14149
14150     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14151
14152     if (moveNumber == forwardMostMove - 1 ||
14153         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14154
14155         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14156
14157         if (strchr(cpThinkOutput, '\n')) {
14158             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14159         }
14160     } else {
14161         *cpThinkOutput = NULLCHAR;
14162     }
14163
14164     /* [AS] Hide thinking from human user */
14165     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14166         *cpThinkOutput = NULLCHAR;
14167         if( thinkOutput[0] != NULLCHAR ) {
14168             int i;
14169
14170             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14171                 cpThinkOutput[i] = '.';
14172             }
14173             cpThinkOutput[i] = NULLCHAR;
14174             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14175         }
14176     }
14177
14178     if (moveNumber == forwardMostMove - 1 &&
14179         gameInfo.resultDetails != NULL) {
14180         if (gameInfo.resultDetails[0] == NULLCHAR) {
14181           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14182         } else {
14183           snprintf(res, MSG_SIZ, " {%s} %s",
14184                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14185         }
14186     } else {
14187         res[0] = NULLCHAR;
14188     }
14189
14190     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14191         DisplayMessage(res, cpThinkOutput);
14192     } else {
14193       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14194                 WhiteOnMove(moveNumber) ? " " : ".. ",
14195                 parseList[moveNumber], res);
14196         DisplayMessage(message, cpThinkOutput);
14197     }
14198 }
14199
14200 void
14201 DisplayComment(moveNumber, text)
14202      int moveNumber;
14203      char *text;
14204 {
14205     char title[MSG_SIZ];
14206     char buf[8000]; // comment can be long!
14207     int score, depth;
14208
14209     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14210       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14211     } else {
14212       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14213               WhiteOnMove(moveNumber) ? " " : ".. ",
14214               parseList[moveNumber]);
14215     }
14216     // [HGM] PV info: display PV info together with (or as) comment
14217     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14218       if(text == NULL) text = "";
14219       score = pvInfoList[moveNumber].score;
14220       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14221               depth, (pvInfoList[moveNumber].time+50)/100, text);
14222       text = buf;
14223     }
14224     if (text != NULL && (appData.autoDisplayComment || commentUp))
14225         CommentPopUp(title, text);
14226 }
14227
14228 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14229  * might be busy thinking or pondering.  It can be omitted if your
14230  * gnuchess is configured to stop thinking immediately on any user
14231  * input.  However, that gnuchess feature depends on the FIONREAD
14232  * ioctl, which does not work properly on some flavors of Unix.
14233  */
14234 void
14235 Attention(cps)
14236      ChessProgramState *cps;
14237 {
14238 #if ATTENTION
14239     if (!cps->useSigint) return;
14240     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14241     switch (gameMode) {
14242       case MachinePlaysWhite:
14243       case MachinePlaysBlack:
14244       case TwoMachinesPlay:
14245       case IcsPlayingWhite:
14246       case IcsPlayingBlack:
14247       case AnalyzeMode:
14248       case AnalyzeFile:
14249         /* Skip if we know it isn't thinking */
14250         if (!cps->maybeThinking) return;
14251         if (appData.debugMode)
14252           fprintf(debugFP, "Interrupting %s\n", cps->which);
14253         InterruptChildProcess(cps->pr);
14254         cps->maybeThinking = FALSE;
14255         break;
14256       default:
14257         break;
14258     }
14259 #endif /*ATTENTION*/
14260 }
14261
14262 int
14263 CheckFlags()
14264 {
14265     if (whiteTimeRemaining <= 0) {
14266         if (!whiteFlag) {
14267             whiteFlag = TRUE;
14268             if (appData.icsActive) {
14269                 if (appData.autoCallFlag &&
14270                     gameMode == IcsPlayingBlack && !blackFlag) {
14271                   SendToICS(ics_prefix);
14272                   SendToICS("flag\n");
14273                 }
14274             } else {
14275                 if (blackFlag) {
14276                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14277                 } else {
14278                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14279                     if (appData.autoCallFlag) {
14280                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14281                         return TRUE;
14282                     }
14283                 }
14284             }
14285         }
14286     }
14287     if (blackTimeRemaining <= 0) {
14288         if (!blackFlag) {
14289             blackFlag = TRUE;
14290             if (appData.icsActive) {
14291                 if (appData.autoCallFlag &&
14292                     gameMode == IcsPlayingWhite && !whiteFlag) {
14293                   SendToICS(ics_prefix);
14294                   SendToICS("flag\n");
14295                 }
14296             } else {
14297                 if (whiteFlag) {
14298                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14299                 } else {
14300                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14301                     if (appData.autoCallFlag) {
14302                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14303                         return TRUE;
14304                     }
14305                 }
14306             }
14307         }
14308     }
14309     return FALSE;
14310 }
14311
14312 void
14313 CheckTimeControl()
14314 {
14315     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14316         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14317
14318     /*
14319      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14320      */
14321     if ( !WhiteOnMove(forwardMostMove) ) {
14322         /* White made time control */
14323         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14324         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14325         /* [HGM] time odds: correct new time quota for time odds! */
14326                                             / WhitePlayer()->timeOdds;
14327         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14328     } else {
14329         lastBlack -= blackTimeRemaining;
14330         /* Black made time control */
14331         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14332                                             / WhitePlayer()->other->timeOdds;
14333         lastWhite = whiteTimeRemaining;
14334     }
14335 }
14336
14337 void
14338 DisplayBothClocks()
14339 {
14340     int wom = gameMode == EditPosition ?
14341       !blackPlaysFirst : WhiteOnMove(currentMove);
14342     DisplayWhiteClock(whiteTimeRemaining, wom);
14343     DisplayBlackClock(blackTimeRemaining, !wom);
14344 }
14345
14346
14347 /* Timekeeping seems to be a portability nightmare.  I think everyone
14348    has ftime(), but I'm really not sure, so I'm including some ifdefs
14349    to use other calls if you don't.  Clocks will be less accurate if
14350    you have neither ftime nor gettimeofday.
14351 */
14352
14353 /* VS 2008 requires the #include outside of the function */
14354 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14355 #include <sys/timeb.h>
14356 #endif
14357
14358 /* Get the current time as a TimeMark */
14359 void
14360 GetTimeMark(tm)
14361      TimeMark *tm;
14362 {
14363 #if HAVE_GETTIMEOFDAY
14364
14365     struct timeval timeVal;
14366     struct timezone timeZone;
14367
14368     gettimeofday(&timeVal, &timeZone);
14369     tm->sec = (long) timeVal.tv_sec;
14370     tm->ms = (int) (timeVal.tv_usec / 1000L);
14371
14372 #else /*!HAVE_GETTIMEOFDAY*/
14373 #if HAVE_FTIME
14374
14375 // include <sys/timeb.h> / moved to just above start of function
14376     struct timeb timeB;
14377
14378     ftime(&timeB);
14379     tm->sec = (long) timeB.time;
14380     tm->ms = (int) timeB.millitm;
14381
14382 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14383     tm->sec = (long) time(NULL);
14384     tm->ms = 0;
14385 #endif
14386 #endif
14387 }
14388
14389 /* Return the difference in milliseconds between two
14390    time marks.  We assume the difference will fit in a long!
14391 */
14392 long
14393 SubtractTimeMarks(tm2, tm1)
14394      TimeMark *tm2, *tm1;
14395 {
14396     return 1000L*(tm2->sec - tm1->sec) +
14397            (long) (tm2->ms - tm1->ms);
14398 }
14399
14400
14401 /*
14402  * Code to manage the game clocks.
14403  *
14404  * In tournament play, black starts the clock and then white makes a move.
14405  * We give the human user a slight advantage if he is playing white---the
14406  * clocks don't run until he makes his first move, so it takes zero time.
14407  * Also, we don't account for network lag, so we could get out of sync
14408  * with GNU Chess's clock -- but then, referees are always right.
14409  */
14410
14411 static TimeMark tickStartTM;
14412 static long intendedTickLength;
14413
14414 long
14415 NextTickLength(timeRemaining)
14416      long timeRemaining;
14417 {
14418     long nominalTickLength, nextTickLength;
14419
14420     if (timeRemaining > 0L && timeRemaining <= 10000L)
14421       nominalTickLength = 100L;
14422     else
14423       nominalTickLength = 1000L;
14424     nextTickLength = timeRemaining % nominalTickLength;
14425     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14426
14427     return nextTickLength;
14428 }
14429
14430 /* Adjust clock one minute up or down */
14431 void
14432 AdjustClock(Boolean which, int dir)
14433 {
14434     if(which) blackTimeRemaining += 60000*dir;
14435     else      whiteTimeRemaining += 60000*dir;
14436     DisplayBothClocks();
14437 }
14438
14439 /* Stop clocks and reset to a fresh time control */
14440 void
14441 ResetClocks()
14442 {
14443     (void) StopClockTimer();
14444     if (appData.icsActive) {
14445         whiteTimeRemaining = blackTimeRemaining = 0;
14446     } else if (searchTime) {
14447         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14448         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14449     } else { /* [HGM] correct new time quote for time odds */
14450         whiteTC = blackTC = fullTimeControlString;
14451         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14452         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14453     }
14454     if (whiteFlag || blackFlag) {
14455         DisplayTitle("");
14456         whiteFlag = blackFlag = FALSE;
14457     }
14458     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14459     DisplayBothClocks();
14460 }
14461
14462 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14463
14464 /* Decrement running clock by amount of time that has passed */
14465 void
14466 DecrementClocks()
14467 {
14468     long timeRemaining;
14469     long lastTickLength, fudge;
14470     TimeMark now;
14471
14472     if (!appData.clockMode) return;
14473     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14474
14475     GetTimeMark(&now);
14476
14477     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14478
14479     /* Fudge if we woke up a little too soon */
14480     fudge = intendedTickLength - lastTickLength;
14481     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14482
14483     if (WhiteOnMove(forwardMostMove)) {
14484         if(whiteNPS >= 0) lastTickLength = 0;
14485         timeRemaining = whiteTimeRemaining -= lastTickLength;
14486         if(timeRemaining < 0 && !appData.icsActive) {
14487             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14488             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14489                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14490                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14491             }
14492         }
14493         DisplayWhiteClock(whiteTimeRemaining - fudge,
14494                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14495     } else {
14496         if(blackNPS >= 0) lastTickLength = 0;
14497         timeRemaining = blackTimeRemaining -= lastTickLength;
14498         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14499             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14500             if(suddenDeath) {
14501                 blackStartMove = forwardMostMove;
14502                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14503             }
14504         }
14505         DisplayBlackClock(blackTimeRemaining - fudge,
14506                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14507     }
14508     if (CheckFlags()) return;
14509
14510     tickStartTM = now;
14511     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14512     StartClockTimer(intendedTickLength);
14513
14514     /* if the time remaining has fallen below the alarm threshold, sound the
14515      * alarm. if the alarm has sounded and (due to a takeback or time control
14516      * with increment) the time remaining has increased to a level above the
14517      * threshold, reset the alarm so it can sound again.
14518      */
14519
14520     if (appData.icsActive && appData.icsAlarm) {
14521
14522         /* make sure we are dealing with the user's clock */
14523         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14524                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14525            )) return;
14526
14527         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14528             alarmSounded = FALSE;
14529         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14530             PlayAlarmSound();
14531             alarmSounded = TRUE;
14532         }
14533     }
14534 }
14535
14536
14537 /* A player has just moved, so stop the previously running
14538    clock and (if in clock mode) start the other one.
14539    We redisplay both clocks in case we're in ICS mode, because
14540    ICS gives us an update to both clocks after every move.
14541    Note that this routine is called *after* forwardMostMove
14542    is updated, so the last fractional tick must be subtracted
14543    from the color that is *not* on move now.
14544 */
14545 void
14546 SwitchClocks(int newMoveNr)
14547 {
14548     long lastTickLength;
14549     TimeMark now;
14550     int flagged = FALSE;
14551
14552     GetTimeMark(&now);
14553
14554     if (StopClockTimer() && appData.clockMode) {
14555         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14556         if (!WhiteOnMove(forwardMostMove)) {
14557             if(blackNPS >= 0) lastTickLength = 0;
14558             blackTimeRemaining -= lastTickLength;
14559            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14560 //         if(pvInfoList[forwardMostMove].time == -1)
14561                  pvInfoList[forwardMostMove].time =               // use GUI time
14562                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14563         } else {
14564            if(whiteNPS >= 0) lastTickLength = 0;
14565            whiteTimeRemaining -= lastTickLength;
14566            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14567 //         if(pvInfoList[forwardMostMove].time == -1)
14568                  pvInfoList[forwardMostMove].time =
14569                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14570         }
14571         flagged = CheckFlags();
14572     }
14573     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14574     CheckTimeControl();
14575
14576     if (flagged || !appData.clockMode) return;
14577
14578     switch (gameMode) {
14579       case MachinePlaysBlack:
14580       case MachinePlaysWhite:
14581       case BeginningOfGame:
14582         if (pausing) return;
14583         break;
14584
14585       case EditGame:
14586       case PlayFromGameFile:
14587       case IcsExamining:
14588         return;
14589
14590       default:
14591         break;
14592     }
14593
14594     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14595         if(WhiteOnMove(forwardMostMove))
14596              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14597         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14598     }
14599
14600     tickStartTM = now;
14601     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14602       whiteTimeRemaining : blackTimeRemaining);
14603     StartClockTimer(intendedTickLength);
14604 }
14605
14606
14607 /* Stop both clocks */
14608 void
14609 StopClocks()
14610 {
14611     long lastTickLength;
14612     TimeMark now;
14613
14614     if (!StopClockTimer()) return;
14615     if (!appData.clockMode) return;
14616
14617     GetTimeMark(&now);
14618
14619     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14620     if (WhiteOnMove(forwardMostMove)) {
14621         if(whiteNPS >= 0) lastTickLength = 0;
14622         whiteTimeRemaining -= lastTickLength;
14623         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14624     } else {
14625         if(blackNPS >= 0) lastTickLength = 0;
14626         blackTimeRemaining -= lastTickLength;
14627         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14628     }
14629     CheckFlags();
14630 }
14631
14632 /* Start clock of player on move.  Time may have been reset, so
14633    if clock is already running, stop and restart it. */
14634 void
14635 StartClocks()
14636 {
14637     (void) StopClockTimer(); /* in case it was running already */
14638     DisplayBothClocks();
14639     if (CheckFlags()) return;
14640
14641     if (!appData.clockMode) return;
14642     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14643
14644     GetTimeMark(&tickStartTM);
14645     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14646       whiteTimeRemaining : blackTimeRemaining);
14647
14648    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14649     whiteNPS = blackNPS = -1;
14650     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14651        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14652         whiteNPS = first.nps;
14653     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14654        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14655         blackNPS = first.nps;
14656     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14657         whiteNPS = second.nps;
14658     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14659         blackNPS = second.nps;
14660     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14661
14662     StartClockTimer(intendedTickLength);
14663 }
14664
14665 char *
14666 TimeString(ms)
14667      long ms;
14668 {
14669     long second, minute, hour, day;
14670     char *sign = "";
14671     static char buf[32];
14672
14673     if (ms > 0 && ms <= 9900) {
14674       /* convert milliseconds to tenths, rounding up */
14675       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14676
14677       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14678       return buf;
14679     }
14680
14681     /* convert milliseconds to seconds, rounding up */
14682     /* use floating point to avoid strangeness of integer division
14683        with negative dividends on many machines */
14684     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14685
14686     if (second < 0) {
14687         sign = "-";
14688         second = -second;
14689     }
14690
14691     day = second / (60 * 60 * 24);
14692     second = second % (60 * 60 * 24);
14693     hour = second / (60 * 60);
14694     second = second % (60 * 60);
14695     minute = second / 60;
14696     second = second % 60;
14697
14698     if (day > 0)
14699       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14700               sign, day, hour, minute, second);
14701     else if (hour > 0)
14702       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14703     else
14704       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14705
14706     return buf;
14707 }
14708
14709
14710 /*
14711  * This is necessary because some C libraries aren't ANSI C compliant yet.
14712  */
14713 char *
14714 StrStr(string, match)
14715      char *string, *match;
14716 {
14717     int i, length;
14718
14719     length = strlen(match);
14720
14721     for (i = strlen(string) - length; i >= 0; i--, string++)
14722       if (!strncmp(match, string, length))
14723         return string;
14724
14725     return NULL;
14726 }
14727
14728 char *
14729 StrCaseStr(string, match)
14730      char *string, *match;
14731 {
14732     int i, j, length;
14733
14734     length = strlen(match);
14735
14736     for (i = strlen(string) - length; i >= 0; i--, string++) {
14737         for (j = 0; j < length; j++) {
14738             if (ToLower(match[j]) != ToLower(string[j]))
14739               break;
14740         }
14741         if (j == length) return string;
14742     }
14743
14744     return NULL;
14745 }
14746
14747 #ifndef _amigados
14748 int
14749 StrCaseCmp(s1, s2)
14750      char *s1, *s2;
14751 {
14752     char c1, c2;
14753
14754     for (;;) {
14755         c1 = ToLower(*s1++);
14756         c2 = ToLower(*s2++);
14757         if (c1 > c2) return 1;
14758         if (c1 < c2) return -1;
14759         if (c1 == NULLCHAR) return 0;
14760     }
14761 }
14762
14763
14764 int
14765 ToLower(c)
14766      int c;
14767 {
14768     return isupper(c) ? tolower(c) : c;
14769 }
14770
14771
14772 int
14773 ToUpper(c)
14774      int c;
14775 {
14776     return islower(c) ? toupper(c) : c;
14777 }
14778 #endif /* !_amigados    */
14779
14780 char *
14781 StrSave(s)
14782      char *s;
14783 {
14784   char *ret;
14785
14786   if ((ret = (char *) malloc(strlen(s) + 1)))
14787     {
14788       safeStrCpy(ret, s, strlen(s)+1);
14789     }
14790   return ret;
14791 }
14792
14793 char *
14794 StrSavePtr(s, savePtr)
14795      char *s, **savePtr;
14796 {
14797     if (*savePtr) {
14798         free(*savePtr);
14799     }
14800     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14801       safeStrCpy(*savePtr, s, strlen(s)+1);
14802     }
14803     return(*savePtr);
14804 }
14805
14806 char *
14807 PGNDate()
14808 {
14809     time_t clock;
14810     struct tm *tm;
14811     char buf[MSG_SIZ];
14812
14813     clock = time((time_t *)NULL);
14814     tm = localtime(&clock);
14815     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14816             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14817     return StrSave(buf);
14818 }
14819
14820
14821 char *
14822 PositionToFEN(move, overrideCastling)
14823      int move;
14824      char *overrideCastling;
14825 {
14826     int i, j, fromX, fromY, toX, toY;
14827     int whiteToPlay;
14828     char buf[128];
14829     char *p, *q;
14830     int emptycount;
14831     ChessSquare piece;
14832
14833     whiteToPlay = (gameMode == EditPosition) ?
14834       !blackPlaysFirst : (move % 2 == 0);
14835     p = buf;
14836
14837     /* Piece placement data */
14838     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14839         emptycount = 0;
14840         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14841             if (boards[move][i][j] == EmptySquare) {
14842                 emptycount++;
14843             } else { ChessSquare piece = boards[move][i][j];
14844                 if (emptycount > 0) {
14845                     if(emptycount<10) /* [HGM] can be >= 10 */
14846                         *p++ = '0' + emptycount;
14847                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14848                     emptycount = 0;
14849                 }
14850                 if(PieceToChar(piece) == '+') {
14851                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14852                     *p++ = '+';
14853                     piece = (ChessSquare)(DEMOTED piece);
14854                 }
14855                 *p++ = PieceToChar(piece);
14856                 if(p[-1] == '~') {
14857                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14858                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14859                     *p++ = '~';
14860                 }
14861             }
14862         }
14863         if (emptycount > 0) {
14864             if(emptycount<10) /* [HGM] can be >= 10 */
14865                 *p++ = '0' + emptycount;
14866             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14867             emptycount = 0;
14868         }
14869         *p++ = '/';
14870     }
14871     *(p - 1) = ' ';
14872
14873     /* [HGM] print Crazyhouse or Shogi holdings */
14874     if( gameInfo.holdingsWidth ) {
14875         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14876         q = p;
14877         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14878             piece = boards[move][i][BOARD_WIDTH-1];
14879             if( piece != EmptySquare )
14880               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14881                   *p++ = PieceToChar(piece);
14882         }
14883         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14884             piece = boards[move][BOARD_HEIGHT-i-1][0];
14885             if( piece != EmptySquare )
14886               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14887                   *p++ = PieceToChar(piece);
14888         }
14889
14890         if( q == p ) *p++ = '-';
14891         *p++ = ']';
14892         *p++ = ' ';
14893     }
14894
14895     /* Active color */
14896     *p++ = whiteToPlay ? 'w' : 'b';
14897     *p++ = ' ';
14898
14899   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14900     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14901   } else {
14902   if(nrCastlingRights) {
14903      q = p;
14904      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14905        /* [HGM] write directly from rights */
14906            if(boards[move][CASTLING][2] != NoRights &&
14907               boards[move][CASTLING][0] != NoRights   )
14908                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14909            if(boards[move][CASTLING][2] != NoRights &&
14910               boards[move][CASTLING][1] != NoRights   )
14911                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14912            if(boards[move][CASTLING][5] != NoRights &&
14913               boards[move][CASTLING][3] != NoRights   )
14914                 *p++ = boards[move][CASTLING][3] + AAA;
14915            if(boards[move][CASTLING][5] != NoRights &&
14916               boards[move][CASTLING][4] != NoRights   )
14917                 *p++ = boards[move][CASTLING][4] + AAA;
14918      } else {
14919
14920         /* [HGM] write true castling rights */
14921         if( nrCastlingRights == 6 ) {
14922             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14923                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14924             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14925                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14926             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14927                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14928             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14929                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14930         }
14931      }
14932      if (q == p) *p++ = '-'; /* No castling rights */
14933      *p++ = ' ';
14934   }
14935
14936   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14937      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14938     /* En passant target square */
14939     if (move > backwardMostMove) {
14940         fromX = moveList[move - 1][0] - AAA;
14941         fromY = moveList[move - 1][1] - ONE;
14942         toX = moveList[move - 1][2] - AAA;
14943         toY = moveList[move - 1][3] - ONE;
14944         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14945             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14946             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14947             fromX == toX) {
14948             /* 2-square pawn move just happened */
14949             *p++ = toX + AAA;
14950             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14951         } else {
14952             *p++ = '-';
14953         }
14954     } else if(move == backwardMostMove) {
14955         // [HGM] perhaps we should always do it like this, and forget the above?
14956         if((signed char)boards[move][EP_STATUS] >= 0) {
14957             *p++ = boards[move][EP_STATUS] + AAA;
14958             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14959         } else {
14960             *p++ = '-';
14961         }
14962     } else {
14963         *p++ = '-';
14964     }
14965     *p++ = ' ';
14966   }
14967   }
14968
14969     /* [HGM] find reversible plies */
14970     {   int i = 0, j=move;
14971
14972         if (appData.debugMode) { int k;
14973             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14974             for(k=backwardMostMove; k<=forwardMostMove; k++)
14975                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14976
14977         }
14978
14979         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14980         if( j == backwardMostMove ) i += initialRulePlies;
14981         sprintf(p, "%d ", i);
14982         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14983     }
14984     /* Fullmove number */
14985     sprintf(p, "%d", (move / 2) + 1);
14986
14987     return StrSave(buf);
14988 }
14989
14990 Boolean
14991 ParseFEN(board, blackPlaysFirst, fen)
14992     Board board;
14993      int *blackPlaysFirst;
14994      char *fen;
14995 {
14996     int i, j;
14997     char *p, c;
14998     int emptycount;
14999     ChessSquare piece;
15000
15001     p = fen;
15002
15003     /* [HGM] by default clear Crazyhouse holdings, if present */
15004     if(gameInfo.holdingsWidth) {
15005        for(i=0; i<BOARD_HEIGHT; i++) {
15006            board[i][0]             = EmptySquare; /* black holdings */
15007            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15008            board[i][1]             = (ChessSquare) 0; /* black counts */
15009            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15010        }
15011     }
15012
15013     /* Piece placement data */
15014     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15015         j = 0;
15016         for (;;) {
15017             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15018                 if (*p == '/') p++;
15019                 emptycount = gameInfo.boardWidth - j;
15020                 while (emptycount--)
15021                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15022                 break;
15023 #if(BOARD_FILES >= 10)
15024             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15025                 p++; emptycount=10;
15026                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15027                 while (emptycount--)
15028                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15029 #endif
15030             } else if (isdigit(*p)) {
15031                 emptycount = *p++ - '0';
15032                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15033                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15034                 while (emptycount--)
15035                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15036             } else if (*p == '+' || isalpha(*p)) {
15037                 if (j >= gameInfo.boardWidth) return FALSE;
15038                 if(*p=='+') {
15039                     piece = CharToPiece(*++p);
15040                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15041                     piece = (ChessSquare) (PROMOTED piece ); p++;
15042                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15043                 } else piece = CharToPiece(*p++);
15044
15045                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15046                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15047                     piece = (ChessSquare) (PROMOTED piece);
15048                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15049                     p++;
15050                 }
15051                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15052             } else {
15053                 return FALSE;
15054             }
15055         }
15056     }
15057     while (*p == '/' || *p == ' ') p++;
15058
15059     /* [HGM] look for Crazyhouse holdings here */
15060     while(*p==' ') p++;
15061     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15062         if(*p == '[') p++;
15063         if(*p == '-' ) p++; /* empty holdings */ else {
15064             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15065             /* if we would allow FEN reading to set board size, we would   */
15066             /* have to add holdings and shift the board read so far here   */
15067             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15068                 p++;
15069                 if((int) piece >= (int) BlackPawn ) {
15070                     i = (int)piece - (int)BlackPawn;
15071                     i = PieceToNumber((ChessSquare)i);
15072                     if( i >= gameInfo.holdingsSize ) return FALSE;
15073                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15074                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15075                 } else {
15076                     i = (int)piece - (int)WhitePawn;
15077                     i = PieceToNumber((ChessSquare)i);
15078                     if( i >= gameInfo.holdingsSize ) return FALSE;
15079                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15080                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15081                 }
15082             }
15083         }
15084         if(*p == ']') p++;
15085     }
15086
15087     while(*p == ' ') p++;
15088
15089     /* Active color */
15090     c = *p++;
15091     if(appData.colorNickNames) {
15092       if( c == appData.colorNickNames[0] ) c = 'w'; else
15093       if( c == appData.colorNickNames[1] ) c = 'b';
15094     }
15095     switch (c) {
15096       case 'w':
15097         *blackPlaysFirst = FALSE;
15098         break;
15099       case 'b':
15100         *blackPlaysFirst = TRUE;
15101         break;
15102       default:
15103         return FALSE;
15104     }
15105
15106     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15107     /* return the extra info in global variiables             */
15108
15109     /* set defaults in case FEN is incomplete */
15110     board[EP_STATUS] = EP_UNKNOWN;
15111     for(i=0; i<nrCastlingRights; i++ ) {
15112         board[CASTLING][i] =
15113             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15114     }   /* assume possible unless obviously impossible */
15115     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15116     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15117     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15118                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15119     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15120     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15121     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15122                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15123     FENrulePlies = 0;
15124
15125     while(*p==' ') p++;
15126     if(nrCastlingRights) {
15127       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15128           /* castling indicator present, so default becomes no castlings */
15129           for(i=0; i<nrCastlingRights; i++ ) {
15130                  board[CASTLING][i] = NoRights;
15131           }
15132       }
15133       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15134              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15135              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15136              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15137         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15138
15139         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15140             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15141             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15142         }
15143         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15144             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15145         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15146                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15147         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15148                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15149         switch(c) {
15150           case'K':
15151               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15152               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15153               board[CASTLING][2] = whiteKingFile;
15154               break;
15155           case'Q':
15156               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15157               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15158               board[CASTLING][2] = whiteKingFile;
15159               break;
15160           case'k':
15161               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15162               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15163               board[CASTLING][5] = blackKingFile;
15164               break;
15165           case'q':
15166               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15167               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15168               board[CASTLING][5] = blackKingFile;
15169           case '-':
15170               break;
15171           default: /* FRC castlings */
15172               if(c >= 'a') { /* black rights */
15173                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15174                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15175                   if(i == BOARD_RGHT) break;
15176                   board[CASTLING][5] = i;
15177                   c -= AAA;
15178                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15179                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15180                   if(c > i)
15181                       board[CASTLING][3] = c;
15182                   else
15183                       board[CASTLING][4] = c;
15184               } else { /* white rights */
15185                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15186                     if(board[0][i] == WhiteKing) break;
15187                   if(i == BOARD_RGHT) break;
15188                   board[CASTLING][2] = i;
15189                   c -= AAA - 'a' + 'A';
15190                   if(board[0][c] >= WhiteKing) break;
15191                   if(c > i)
15192                       board[CASTLING][0] = c;
15193                   else
15194                       board[CASTLING][1] = c;
15195               }
15196         }
15197       }
15198       for(i=0; i<nrCastlingRights; i++)
15199         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15200     if (appData.debugMode) {
15201         fprintf(debugFP, "FEN castling rights:");
15202         for(i=0; i<nrCastlingRights; i++)
15203         fprintf(debugFP, " %d", board[CASTLING][i]);
15204         fprintf(debugFP, "\n");
15205     }
15206
15207       while(*p==' ') p++;
15208     }
15209
15210     /* read e.p. field in games that know e.p. capture */
15211     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15212        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15213       if(*p=='-') {
15214         p++; board[EP_STATUS] = EP_NONE;
15215       } else {
15216          char c = *p++ - AAA;
15217
15218          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15219          if(*p >= '0' && *p <='9') p++;
15220          board[EP_STATUS] = c;
15221       }
15222     }
15223
15224
15225     if(sscanf(p, "%d", &i) == 1) {
15226         FENrulePlies = i; /* 50-move ply counter */
15227         /* (The move number is still ignored)    */
15228     }
15229
15230     return TRUE;
15231 }
15232
15233 void
15234 EditPositionPasteFEN(char *fen)
15235 {
15236   if (fen != NULL) {
15237     Board initial_position;
15238
15239     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15240       DisplayError(_("Bad FEN position in clipboard"), 0);
15241       return ;
15242     } else {
15243       int savedBlackPlaysFirst = blackPlaysFirst;
15244       EditPositionEvent();
15245       blackPlaysFirst = savedBlackPlaysFirst;
15246       CopyBoard(boards[0], initial_position);
15247       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15248       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15249       DisplayBothClocks();
15250       DrawPosition(FALSE, boards[currentMove]);
15251     }
15252   }
15253 }
15254
15255 static char cseq[12] = "\\   ";
15256
15257 Boolean set_cont_sequence(char *new_seq)
15258 {
15259     int len;
15260     Boolean ret;
15261
15262     // handle bad attempts to set the sequence
15263         if (!new_seq)
15264                 return 0; // acceptable error - no debug
15265
15266     len = strlen(new_seq);
15267     ret = (len > 0) && (len < sizeof(cseq));
15268     if (ret)
15269       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15270     else if (appData.debugMode)
15271       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15272     return ret;
15273 }
15274
15275 /*
15276     reformat a source message so words don't cross the width boundary.  internal
15277     newlines are not removed.  returns the wrapped size (no null character unless
15278     included in source message).  If dest is NULL, only calculate the size required
15279     for the dest buffer.  lp argument indicats line position upon entry, and it's
15280     passed back upon exit.
15281 */
15282 int wrap(char *dest, char *src, int count, int width, int *lp)
15283 {
15284     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15285
15286     cseq_len = strlen(cseq);
15287     old_line = line = *lp;
15288     ansi = len = clen = 0;
15289
15290     for (i=0; i < count; i++)
15291     {
15292         if (src[i] == '\033')
15293             ansi = 1;
15294
15295         // if we hit the width, back up
15296         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15297         {
15298             // store i & len in case the word is too long
15299             old_i = i, old_len = len;
15300
15301             // find the end of the last word
15302             while (i && src[i] != ' ' && src[i] != '\n')
15303             {
15304                 i--;
15305                 len--;
15306             }
15307
15308             // word too long?  restore i & len before splitting it
15309             if ((old_i-i+clen) >= width)
15310             {
15311                 i = old_i;
15312                 len = old_len;
15313             }
15314
15315             // extra space?
15316             if (i && src[i-1] == ' ')
15317                 len--;
15318
15319             if (src[i] != ' ' && src[i] != '\n')
15320             {
15321                 i--;
15322                 if (len)
15323                     len--;
15324             }
15325
15326             // now append the newline and continuation sequence
15327             if (dest)
15328                 dest[len] = '\n';
15329             len++;
15330             if (dest)
15331                 strncpy(dest+len, cseq, cseq_len);
15332             len += cseq_len;
15333             line = cseq_len;
15334             clen = cseq_len;
15335             continue;
15336         }
15337
15338         if (dest)
15339             dest[len] = src[i];
15340         len++;
15341         if (!ansi)
15342             line++;
15343         if (src[i] == '\n')
15344             line = 0;
15345         if (src[i] == 'm')
15346             ansi = 0;
15347     }
15348     if (dest && appData.debugMode)
15349     {
15350         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15351             count, width, line, len, *lp);
15352         show_bytes(debugFP, src, count);
15353         fprintf(debugFP, "\ndest: ");
15354         show_bytes(debugFP, dest, len);
15355         fprintf(debugFP, "\n");
15356     }
15357     *lp = dest ? line : old_line;
15358
15359     return len;
15360 }
15361
15362 // [HGM] vari: routines for shelving variations
15363
15364 void
15365 PushTail(int firstMove, int lastMove)
15366 {
15367         int i, j, nrMoves = lastMove - firstMove;
15368
15369         if(appData.icsActive) { // only in local mode
15370                 forwardMostMove = currentMove; // mimic old ICS behavior
15371                 return;
15372         }
15373         if(storedGames >= MAX_VARIATIONS-1) return;
15374
15375         // push current tail of game on stack
15376         savedResult[storedGames] = gameInfo.result;
15377         savedDetails[storedGames] = gameInfo.resultDetails;
15378         gameInfo.resultDetails = NULL;
15379         savedFirst[storedGames] = firstMove;
15380         savedLast [storedGames] = lastMove;
15381         savedFramePtr[storedGames] = framePtr;
15382         framePtr -= nrMoves; // reserve space for the boards
15383         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15384             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15385             for(j=0; j<MOVE_LEN; j++)
15386                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15387             for(j=0; j<2*MOVE_LEN; j++)
15388                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15389             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15390             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15391             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15392             pvInfoList[firstMove+i-1].depth = 0;
15393             commentList[framePtr+i] = commentList[firstMove+i];
15394             commentList[firstMove+i] = NULL;
15395         }
15396
15397         storedGames++;
15398         forwardMostMove = firstMove; // truncate game so we can start variation
15399         if(storedGames == 1) GreyRevert(FALSE);
15400 }
15401
15402 Boolean
15403 PopTail(Boolean annotate)
15404 {
15405         int i, j, nrMoves;
15406         char buf[8000], moveBuf[20];
15407
15408         if(appData.icsActive) return FALSE; // only in local mode
15409         if(!storedGames) return FALSE; // sanity
15410         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15411
15412         storedGames--;
15413         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15414         nrMoves = savedLast[storedGames] - currentMove;
15415         if(annotate) {
15416                 int cnt = 10;
15417                 if(!WhiteOnMove(currentMove))
15418                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15419                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15420                 for(i=currentMove; i<forwardMostMove; i++) {
15421                         if(WhiteOnMove(i))
15422                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15423                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15424                         strcat(buf, moveBuf);
15425                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15426                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15427                 }
15428                 strcat(buf, ")");
15429         }
15430         for(i=1; i<=nrMoves; i++) { // copy last variation back
15431             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15432             for(j=0; j<MOVE_LEN; j++)
15433                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15434             for(j=0; j<2*MOVE_LEN; j++)
15435                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15436             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15437             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15438             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15439             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15440             commentList[currentMove+i] = commentList[framePtr+i];
15441             commentList[framePtr+i] = NULL;
15442         }
15443         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15444         framePtr = savedFramePtr[storedGames];
15445         gameInfo.result = savedResult[storedGames];
15446         if(gameInfo.resultDetails != NULL) {
15447             free(gameInfo.resultDetails);
15448       }
15449         gameInfo.resultDetails = savedDetails[storedGames];
15450         forwardMostMove = currentMove + nrMoves;
15451         if(storedGames == 0) GreyRevert(TRUE);
15452         return TRUE;
15453 }
15454
15455 void
15456 CleanupTail()
15457 {       // remove all shelved variations
15458         int i;
15459         for(i=0; i<storedGames; i++) {
15460             if(savedDetails[i])
15461                 free(savedDetails[i]);
15462             savedDetails[i] = NULL;
15463         }
15464         for(i=framePtr; i<MAX_MOVES; i++) {
15465                 if(commentList[i]) free(commentList[i]);
15466                 commentList[i] = NULL;
15467         }
15468         framePtr = MAX_MOVES-1;
15469         storedGames = 0;
15470 }
15471
15472 void
15473 LoadVariation(int index, char *text)
15474 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15475         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15476         int level = 0, move;
15477
15478         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15479         // first find outermost bracketing variation
15480         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15481             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15482                 if(*p == '{') wait = '}'; else
15483                 if(*p == '[') wait = ']'; else
15484                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15485                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15486             }
15487             if(*p == wait) wait = NULLCHAR; // closing ]} found
15488             p++;
15489         }
15490         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15491         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15492         end[1] = NULLCHAR; // clip off comment beyond variation
15493         ToNrEvent(currentMove-1);
15494         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15495         // kludge: use ParsePV() to append variation to game
15496         move = currentMove;
15497         ParsePV(start, TRUE);
15498         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15499         ClearPremoveHighlights();
15500         CommentPopDown();
15501         ToNrEvent(currentMove+1);
15502 }
15503