updated parser.c form parser.l
[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(channel == atoi(chatPartner[p])) {
2865                     talker[0] = '['; strcat(talker, "] ");
2866                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2867                     chattingPartner = p; break;
2868                     }
2869                 } else
2870                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2871                 for(p=0; p<MAX_CHAT; p++) {
2872                     if(!strcmp("kibitzes", chatPartner[p])) {
2873                         talker[0] = '['; strcat(talker, "] ");
2874                         chattingPartner = p; break;
2875                     }
2876                 } else
2877                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2878                 for(p=0; p<MAX_CHAT; p++) {
2879                     if(!strcmp("whispers", chatPartner[p])) {
2880                         talker[0] = '['; strcat(talker, "] ");
2881                         chattingPartner = p; break;
2882                     }
2883                 } else
2884                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2885                   if(buf[i-8] == '-' && buf[i-3] == 't')
2886                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2887                     if(!strcmp("c-shouts", chatPartner[p])) {
2888                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2889                         chattingPartner = p; break;
2890                     }
2891                   }
2892                   if(chattingPartner < 0)
2893                   for(p=0; p<MAX_CHAT; p++) {
2894                     if(!strcmp("shouts", chatPartner[p])) {
2895                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2896                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2897                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2898                         chattingPartner = p; break;
2899                     }
2900                   }
2901                 }
2902                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2903                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2904                     talker[0] = 0; Colorize(ColorTell, FALSE);
2905                     chattingPartner = p; break;
2906                 }
2907                 if(chattingPartner<0) i = oldi; else {
2908                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2909                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2910                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2911                     started = STARTED_COMMENT;
2912                     parse_pos = 0; parse[0] = NULLCHAR;
2913                     savingComment = 3 + chattingPartner; // counts as TRUE
2914                     suppressKibitz = TRUE;
2915                     continue;
2916                 }
2917             } // [HGM] chat: end of patch
2918
2919             if (appData.zippyTalk || appData.zippyPlay) {
2920                 /* [DM] Backup address for color zippy lines */
2921                 backup = i;
2922 #if ZIPPY
2923                if (loggedOn == TRUE)
2924                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2925                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2926 #endif
2927             } // [DM] 'else { ' deleted
2928                 if (
2929                     /* Regular tells and says */
2930                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2931                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2932                     looking_at(buf, &i, "* says: ") ||
2933                     /* Don't color "message" or "messages" output */
2934                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2935                     looking_at(buf, &i, "*. * at *:*: ") ||
2936                     looking_at(buf, &i, "--* (*:*): ") ||
2937                     /* Message notifications (same color as tells) */
2938                     looking_at(buf, &i, "* has left a message ") ||
2939                     looking_at(buf, &i, "* just sent you a message:\n") ||
2940                     /* Whispers and kibitzes */
2941                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2942                     looking_at(buf, &i, "* kibitzes: ") ||
2943                     /* Channel tells */
2944                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2945
2946                   if (tkind == 1 && strchr(star_match[0], ':')) {
2947                       /* Avoid "tells you:" spoofs in channels */
2948                      tkind = 3;
2949                   }
2950                   if (star_match[0][0] == NULLCHAR ||
2951                       strchr(star_match[0], ' ') ||
2952                       (tkind == 3 && strchr(star_match[1], ' '))) {
2953                     /* Reject bogus matches */
2954                     i = oldi;
2955                   } else {
2956                     if (appData.colorize) {
2957                       if (oldi > next_out) {
2958                         SendToPlayer(&buf[next_out], oldi - next_out);
2959                         next_out = oldi;
2960                       }
2961                       switch (tkind) {
2962                       case 1:
2963                         Colorize(ColorTell, FALSE);
2964                         curColor = ColorTell;
2965                         break;
2966                       case 2:
2967                         Colorize(ColorKibitz, FALSE);
2968                         curColor = ColorKibitz;
2969                         break;
2970                       case 3:
2971                         p = strrchr(star_match[1], '(');
2972                         if (p == NULL) {
2973                           p = star_match[1];
2974                         } else {
2975                           p++;
2976                         }
2977                         if (atoi(p) == 1) {
2978                           Colorize(ColorChannel1, FALSE);
2979                           curColor = ColorChannel1;
2980                         } else {
2981                           Colorize(ColorChannel, FALSE);
2982                           curColor = ColorChannel;
2983                         }
2984                         break;
2985                       case 5:
2986                         curColor = ColorNormal;
2987                         break;
2988                       }
2989                     }
2990                     if (started == STARTED_NONE && appData.autoComment &&
2991                         (gameMode == IcsObserving ||
2992                          gameMode == IcsPlayingWhite ||
2993                          gameMode == IcsPlayingBlack)) {
2994                       parse_pos = i - oldi;
2995                       memcpy(parse, &buf[oldi], parse_pos);
2996                       parse[parse_pos] = NULLCHAR;
2997                       started = STARTED_COMMENT;
2998                       savingComment = TRUE;
2999                     } else {
3000                       started = STARTED_CHATTER;
3001                       savingComment = FALSE;
3002                     }
3003                     loggedOn = TRUE;
3004                     continue;
3005                   }
3006                 }
3007
3008                 if (looking_at(buf, &i, "* s-shouts: ") ||
3009                     looking_at(buf, &i, "* c-shouts: ")) {
3010                     if (appData.colorize) {
3011                         if (oldi > next_out) {
3012                             SendToPlayer(&buf[next_out], oldi - next_out);
3013                             next_out = oldi;
3014                         }
3015                         Colorize(ColorSShout, FALSE);
3016                         curColor = ColorSShout;
3017                     }
3018                     loggedOn = TRUE;
3019                     started = STARTED_CHATTER;
3020                     continue;
3021                 }
3022
3023                 if (looking_at(buf, &i, "--->")) {
3024                     loggedOn = TRUE;
3025                     continue;
3026                 }
3027
3028                 if (looking_at(buf, &i, "* shouts: ") ||
3029                     looking_at(buf, &i, "--> ")) {
3030                     if (appData.colorize) {
3031                         if (oldi > next_out) {
3032                             SendToPlayer(&buf[next_out], oldi - next_out);
3033                             next_out = oldi;
3034                         }
3035                         Colorize(ColorShout, FALSE);
3036                         curColor = ColorShout;
3037                     }
3038                     loggedOn = TRUE;
3039                     started = STARTED_CHATTER;
3040                     continue;
3041                 }
3042
3043                 if (looking_at( buf, &i, "Challenge:")) {
3044                     if (appData.colorize) {
3045                         if (oldi > next_out) {
3046                             SendToPlayer(&buf[next_out], oldi - next_out);
3047                             next_out = oldi;
3048                         }
3049                         Colorize(ColorChallenge, FALSE);
3050                         curColor = ColorChallenge;
3051                     }
3052                     loggedOn = TRUE;
3053                     continue;
3054                 }
3055
3056                 if (looking_at(buf, &i, "* offers you") ||
3057                     looking_at(buf, &i, "* offers to be") ||
3058                     looking_at(buf, &i, "* would like to") ||
3059                     looking_at(buf, &i, "* requests to") ||
3060                     looking_at(buf, &i, "Your opponent offers") ||
3061                     looking_at(buf, &i, "Your opponent requests")) {
3062
3063                     if (appData.colorize) {
3064                         if (oldi > next_out) {
3065                             SendToPlayer(&buf[next_out], oldi - next_out);
3066                             next_out = oldi;
3067                         }
3068                         Colorize(ColorRequest, FALSE);
3069                         curColor = ColorRequest;
3070                     }
3071                     continue;
3072                 }
3073
3074                 if (looking_at(buf, &i, "* (*) seeking")) {
3075                     if (appData.colorize) {
3076                         if (oldi > next_out) {
3077                             SendToPlayer(&buf[next_out], oldi - next_out);
3078                             next_out = oldi;
3079                         }
3080                         Colorize(ColorSeek, FALSE);
3081                         curColor = ColorSeek;
3082                     }
3083                     continue;
3084             }
3085
3086             if (looking_at(buf, &i, "\\   ")) {
3087                 if (prevColor != ColorNormal) {
3088                     if (oldi > next_out) {
3089                         SendToPlayer(&buf[next_out], oldi - next_out);
3090                         next_out = oldi;
3091                     }
3092                     Colorize(prevColor, TRUE);
3093                     curColor = prevColor;
3094                 }
3095                 if (savingComment) {
3096                     parse_pos = i - oldi;
3097                     memcpy(parse, &buf[oldi], parse_pos);
3098                     parse[parse_pos] = NULLCHAR;
3099                     started = STARTED_COMMENT;
3100                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3101                         chattingPartner = savingComment - 3; // kludge to remember the box
3102                 } else {
3103                     started = STARTED_CHATTER;
3104                 }
3105                 continue;
3106             }
3107
3108             if (looking_at(buf, &i, "Black Strength :") ||
3109                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3110                 looking_at(buf, &i, "<10>") ||
3111                 looking_at(buf, &i, "#@#")) {
3112                 /* Wrong board style */
3113                 loggedOn = TRUE;
3114                 SendToICS(ics_prefix);
3115                 SendToICS("set style 12\n");
3116                 SendToICS(ics_prefix);
3117                 SendToICS("refresh\n");
3118                 continue;
3119             }
3120
3121             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3122                 ICSInitScript();
3123                 have_sent_ICS_logon = 1;
3124                 continue;
3125             }
3126
3127             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3128                 (looking_at(buf, &i, "\n<12> ") ||
3129                  looking_at(buf, &i, "<12> "))) {
3130                 loggedOn = TRUE;
3131                 if (oldi > next_out) {
3132                     SendToPlayer(&buf[next_out], oldi - next_out);
3133                 }
3134                 next_out = i;
3135                 started = STARTED_BOARD;
3136                 parse_pos = 0;
3137                 continue;
3138             }
3139
3140             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3141                 looking_at(buf, &i, "<b1> ")) {
3142                 if (oldi > next_out) {
3143                     SendToPlayer(&buf[next_out], oldi - next_out);
3144                 }
3145                 next_out = i;
3146                 started = STARTED_HOLDINGS;
3147                 parse_pos = 0;
3148                 continue;
3149             }
3150
3151             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3152                 loggedOn = TRUE;
3153                 /* Header for a move list -- first line */
3154
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     switch (gameMode) {
3158                       case IcsIdle:
3159                       case BeginningOfGame:
3160                         /* User typed "moves" or "oldmoves" while we
3161                            were idle.  Pretend we asked for these
3162                            moves and soak them up so user can step
3163                            through them and/or save them.
3164                            */
3165                         Reset(FALSE, TRUE);
3166                         gameMode = IcsObserving;
3167                         ModeHighlight();
3168                         ics_gamenum = -1;
3169                         ics_getting_history = H_GOT_UNREQ_HEADER;
3170                         break;
3171                       case EditGame: /*?*/
3172                       case EditPosition: /*?*/
3173                         /* Should above feature work in these modes too? */
3174                         /* For now it doesn't */
3175                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3176                         break;
3177                       default:
3178                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3179                         break;
3180                     }
3181                     break;
3182                   case H_REQUESTED:
3183                     /* Is this the right one? */
3184                     if (gameInfo.white && gameInfo.black &&
3185                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3186                         strcmp(gameInfo.black, star_match[2]) == 0) {
3187                         /* All is well */
3188                         ics_getting_history = H_GOT_REQ_HEADER;
3189                     }
3190                     break;
3191                   case H_GOT_REQ_HEADER:
3192                   case H_GOT_UNREQ_HEADER:
3193                   case H_GOT_UNWANTED_HEADER:
3194                   case H_GETTING_MOVES:
3195                     /* Should not happen */
3196                     DisplayError(_("Error gathering move list: two headers"), 0);
3197                     ics_getting_history = H_FALSE;
3198                     break;
3199                 }
3200
3201                 /* Save player ratings into gameInfo if needed */
3202                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3203                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3204                     (gameInfo.whiteRating == -1 ||
3205                      gameInfo.blackRating == -1)) {
3206
3207                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3208                     gameInfo.blackRating = string_to_rating(star_match[3]);
3209                     if (appData.debugMode)
3210                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3211                               gameInfo.whiteRating, gameInfo.blackRating);
3212                 }
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i,
3217               "* * match, initial time: * minute*, increment: * second")) {
3218                 /* Header for a move list -- second line */
3219                 /* Initial board will follow if this is a wild game */
3220                 if (gameInfo.event != NULL) free(gameInfo.event);
3221                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3222                 gameInfo.event = StrSave(str);
3223                 /* [HGM] we switched variant. Translate boards if needed. */
3224                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3225                 continue;
3226             }
3227
3228             if (looking_at(buf, &i, "Move  ")) {
3229                 /* Beginning of a move list */
3230                 switch (ics_getting_history) {
3231                   case H_FALSE:
3232                     /* Normally should not happen */
3233                     /* Maybe user hit reset while we were parsing */
3234                     break;
3235                   case H_REQUESTED:
3236                     /* Happens if we are ignoring a move list that is not
3237                      * the one we just requested.  Common if the user
3238                      * tries to observe two games without turning off
3239                      * getMoveList */
3240                     break;
3241                   case H_GETTING_MOVES:
3242                     /* Should not happen */
3243                     DisplayError(_("Error gathering move list: nested"), 0);
3244                     ics_getting_history = H_FALSE;
3245                     break;
3246                   case H_GOT_REQ_HEADER:
3247                     ics_getting_history = H_GETTING_MOVES;
3248                     started = STARTED_MOVES;
3249                     parse_pos = 0;
3250                     if (oldi > next_out) {
3251                         SendToPlayer(&buf[next_out], oldi - next_out);
3252                     }
3253                     break;
3254                   case H_GOT_UNREQ_HEADER:
3255                     ics_getting_history = H_GETTING_MOVES;
3256                     started = STARTED_MOVES_NOHIDE;
3257                     parse_pos = 0;
3258                     break;
3259                   case H_GOT_UNWANTED_HEADER:
3260                     ics_getting_history = H_FALSE;
3261                     break;
3262                 }
3263                 continue;
3264             }
3265
3266             if (looking_at(buf, &i, "% ") ||
3267                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3268                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3269                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3270                     soughtPending = FALSE;
3271                     seekGraphUp = TRUE;
3272                     DrawSeekGraph();
3273                 }
3274                 if(suppressKibitz) next_out = i;
3275                 savingComment = FALSE;
3276                 suppressKibitz = 0;
3277                 switch (started) {
3278                   case STARTED_MOVES:
3279                   case STARTED_MOVES_NOHIDE:
3280                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3281                     parse[parse_pos + i - oldi] = NULLCHAR;
3282                     ParseGameHistory(parse);
3283 #if ZIPPY
3284                     if (appData.zippyPlay && first.initDone) {
3285                         FeedMovesToProgram(&first, forwardMostMove);
3286                         if (gameMode == IcsPlayingWhite) {
3287                             if (WhiteOnMove(forwardMostMove)) {
3288                                 if (first.sendTime) {
3289                                   if (first.useColors) {
3290                                     SendToProgram("black\n", &first);
3291                                   }
3292                                   SendTimeRemaining(&first, TRUE);
3293                                 }
3294                                 if (first.useColors) {
3295                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3296                                 }
3297                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3298                                 first.maybeThinking = TRUE;
3299                             } else {
3300                                 if (first.usePlayother) {
3301                                   if (first.sendTime) {
3302                                     SendTimeRemaining(&first, TRUE);
3303                                   }
3304                                   SendToProgram("playother\n", &first);
3305                                   firstMove = FALSE;
3306                                 } else {
3307                                   firstMove = TRUE;
3308                                 }
3309                             }
3310                         } else if (gameMode == IcsPlayingBlack) {
3311                             if (!WhiteOnMove(forwardMostMove)) {
3312                                 if (first.sendTime) {
3313                                   if (first.useColors) {
3314                                     SendToProgram("white\n", &first);
3315                                   }
3316                                   SendTimeRemaining(&first, FALSE);
3317                                 }
3318                                 if (first.useColors) {
3319                                   SendToProgram("black\n", &first);
3320                                 }
3321                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3322                                 first.maybeThinking = TRUE;
3323                             } else {
3324                                 if (first.usePlayother) {
3325                                   if (first.sendTime) {
3326                                     SendTimeRemaining(&first, FALSE);
3327                                   }
3328                                   SendToProgram("playother\n", &first);
3329                                   firstMove = FALSE;
3330                                 } else {
3331                                   firstMove = TRUE;
3332                                 }
3333                             }
3334                         }
3335                     }
3336 #endif
3337                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3338                         /* Moves came from oldmoves or moves command
3339                            while we weren't doing anything else.
3340                            */
3341                         currentMove = forwardMostMove;
3342                         ClearHighlights();/*!!could figure this out*/
3343                         flipView = appData.flipView;
3344                         DrawPosition(TRUE, boards[currentMove]);
3345                         DisplayBothClocks();
3346                         snprintf(str, MSG_SIZ, "%s vs. %s",
3347                                 gameInfo.white, gameInfo.black);
3348                         DisplayTitle(str);
3349                         gameMode = IcsIdle;
3350                     } else {
3351                         /* Moves were history of an active game */
3352                         if (gameInfo.resultDetails != NULL) {
3353                             free(gameInfo.resultDetails);
3354                             gameInfo.resultDetails = NULL;
3355                         }
3356                     }
3357                     HistorySet(parseList, backwardMostMove,
3358                                forwardMostMove, currentMove-1);
3359                     DisplayMove(currentMove - 1);
3360                     if (started == STARTED_MOVES) next_out = i;
3361                     started = STARTED_NONE;
3362                     ics_getting_history = H_FALSE;
3363                     break;
3364
3365                   case STARTED_OBSERVE:
3366                     started = STARTED_NONE;
3367                     SendToICS(ics_prefix);
3368                     SendToICS("refresh\n");
3369                     break;
3370
3371                   default:
3372                     break;
3373                 }
3374                 if(bookHit) { // [HGM] book: simulate book reply
3375                     static char bookMove[MSG_SIZ]; // a bit generous?
3376
3377                     programStats.nodes = programStats.depth = programStats.time =
3378                     programStats.score = programStats.got_only_move = 0;
3379                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3380
3381                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3382                     strcat(bookMove, bookHit);
3383                     HandleMachineMove(bookMove, &first);
3384                 }
3385                 continue;
3386             }
3387
3388             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3389                  started == STARTED_HOLDINGS ||
3390                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3391                 /* Accumulate characters in move list or board */
3392                 parse[parse_pos++] = buf[i];
3393             }
3394
3395             /* Start of game messages.  Mostly we detect start of game
3396                when the first board image arrives.  On some versions
3397                of the ICS, though, we need to do a "refresh" after starting
3398                to observe in order to get the current board right away. */
3399             if (looking_at(buf, &i, "Adding game * to observation list")) {
3400                 started = STARTED_OBSERVE;
3401                 continue;
3402             }
3403
3404             /* Handle auto-observe */
3405             if (appData.autoObserve &&
3406                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3407                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3408                 char *player;
3409                 /* Choose the player that was highlighted, if any. */
3410                 if (star_match[0][0] == '\033' ||
3411                     star_match[1][0] != '\033') {
3412                     player = star_match[0];
3413                 } else {
3414                     player = star_match[2];
3415                 }
3416                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3417                         ics_prefix, StripHighlightAndTitle(player));
3418                 SendToICS(str);
3419
3420                 /* Save ratings from notify string */
3421                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3422                 player1Rating = string_to_rating(star_match[1]);
3423                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3424                 player2Rating = string_to_rating(star_match[3]);
3425
3426                 if (appData.debugMode)
3427                   fprintf(debugFP,
3428                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3429                           player1Name, player1Rating,
3430                           player2Name, player2Rating);
3431
3432                 continue;
3433             }
3434
3435             /* Deal with automatic examine mode after a game,
3436                and with IcsObserving -> IcsExamining transition */
3437             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3438                 looking_at(buf, &i, "has made you an examiner of game *")) {
3439
3440                 int gamenum = atoi(star_match[0]);
3441                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3442                     gamenum == ics_gamenum) {
3443                     /* We were already playing or observing this game;
3444                        no need to refetch history */
3445                     gameMode = IcsExamining;
3446                     if (pausing) {
3447                         pauseExamForwardMostMove = forwardMostMove;
3448                     } else if (currentMove < forwardMostMove) {
3449                         ForwardInner(forwardMostMove);
3450                     }
3451                 } else {
3452                     /* I don't think this case really can happen */
3453                     SendToICS(ics_prefix);
3454                     SendToICS("refresh\n");
3455                 }
3456                 continue;
3457             }
3458
3459             /* Error messages */
3460 //          if (ics_user_moved) {
3461             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3462                 if (looking_at(buf, &i, "Illegal move") ||
3463                     looking_at(buf, &i, "Not a legal move") ||
3464                     looking_at(buf, &i, "Your king is in check") ||
3465                     looking_at(buf, &i, "It isn't your turn") ||
3466                     looking_at(buf, &i, "It is not your move")) {
3467                     /* Illegal move */
3468                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3469                         currentMove = forwardMostMove-1;
3470                         DisplayMove(currentMove - 1); /* before DMError */
3471                         DrawPosition(FALSE, boards[currentMove]);
3472                         SwitchClocks(forwardMostMove-1); // [HGM] race
3473                         DisplayBothClocks();
3474                     }
3475                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3476                     ics_user_moved = 0;
3477                     continue;
3478                 }
3479             }
3480
3481             if (looking_at(buf, &i, "still have time") ||
3482                 looking_at(buf, &i, "not out of time") ||
3483                 looking_at(buf, &i, "either player is out of time") ||
3484                 looking_at(buf, &i, "has timeseal; checking")) {
3485                 /* We must have called his flag a little too soon */
3486                 whiteFlag = blackFlag = FALSE;
3487                 continue;
3488             }
3489
3490             if (looking_at(buf, &i, "added * seconds to") ||
3491                 looking_at(buf, &i, "seconds were added to")) {
3492                 /* Update the clocks */
3493                 SendToICS(ics_prefix);
3494                 SendToICS("refresh\n");
3495                 continue;
3496             }
3497
3498             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3499                 ics_clock_paused = TRUE;
3500                 StopClocks();
3501                 continue;
3502             }
3503
3504             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3505                 ics_clock_paused = FALSE;
3506                 StartClocks();
3507                 continue;
3508             }
3509
3510             /* Grab player ratings from the Creating: message.
3511                Note we have to check for the special case when
3512                the ICS inserts things like [white] or [black]. */
3513             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3514                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3515                 /* star_matches:
3516                    0    player 1 name (not necessarily white)
3517                    1    player 1 rating
3518                    2    empty, white, or black (IGNORED)
3519                    3    player 2 name (not necessarily black)
3520                    4    player 2 rating
3521
3522                    The names/ratings are sorted out when the game
3523                    actually starts (below).
3524                 */
3525                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3526                 player1Rating = string_to_rating(star_match[1]);
3527                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3528                 player2Rating = string_to_rating(star_match[4]);
3529
3530                 if (appData.debugMode)
3531                   fprintf(debugFP,
3532                           "Ratings from 'Creating:' %s %d, %s %d\n",
3533                           player1Name, player1Rating,
3534                           player2Name, player2Rating);
3535
3536                 continue;
3537             }
3538
3539             /* Improved generic start/end-of-game messages */
3540             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3541                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3542                 /* If tkind == 0: */
3543                 /* star_match[0] is the game number */
3544                 /*           [1] is the white player's name */
3545                 /*           [2] is the black player's name */
3546                 /* For end-of-game: */
3547                 /*           [3] is the reason for the game end */
3548                 /*           [4] is a PGN end game-token, preceded by " " */
3549                 /* For start-of-game: */
3550                 /*           [3] begins with "Creating" or "Continuing" */
3551                 /*           [4] is " *" or empty (don't care). */
3552                 int gamenum = atoi(star_match[0]);
3553                 char *whitename, *blackname, *why, *endtoken;
3554                 ChessMove endtype = EndOfFile;
3555
3556                 if (tkind == 0) {
3557                   whitename = star_match[1];
3558                   blackname = star_match[2];
3559                   why = star_match[3];
3560                   endtoken = star_match[4];
3561                 } else {
3562                   whitename = star_match[1];
3563                   blackname = star_match[3];
3564                   why = star_match[5];
3565                   endtoken = star_match[6];
3566                 }
3567
3568                 /* Game start messages */
3569                 if (strncmp(why, "Creating ", 9) == 0 ||
3570                     strncmp(why, "Continuing ", 11) == 0) {
3571                     gs_gamenum = gamenum;
3572                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3573                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3574 #if ZIPPY
3575                     if (appData.zippyPlay) {
3576                         ZippyGameStart(whitename, blackname);
3577                     }
3578 #endif /*ZIPPY*/
3579                     partnerBoardValid = FALSE; // [HGM] bughouse
3580                     continue;
3581                 }
3582
3583                 /* Game end messages */
3584                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3585                     ics_gamenum != gamenum) {
3586                     continue;
3587                 }
3588                 while (endtoken[0] == ' ') endtoken++;
3589                 switch (endtoken[0]) {
3590                   case '*':
3591                   default:
3592                     endtype = GameUnfinished;
3593                     break;
3594                   case '0':
3595                     endtype = BlackWins;
3596                     break;
3597                   case '1':
3598                     if (endtoken[1] == '/')
3599                       endtype = GameIsDrawn;
3600                     else
3601                       endtype = WhiteWins;
3602                     break;
3603                 }
3604                 GameEnds(endtype, why, GE_ICS);
3605 #if ZIPPY
3606                 if (appData.zippyPlay && first.initDone) {
3607                     ZippyGameEnd(endtype, why);
3608                     if (first.pr == NULL) {
3609                       /* Start the next process early so that we'll
3610                          be ready for the next challenge */
3611                       StartChessProgram(&first);
3612                     }
3613                     /* Send "new" early, in case this command takes
3614                        a long time to finish, so that we'll be ready
3615                        for the next challenge. */
3616                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3617                     Reset(TRUE, TRUE);
3618                 }
3619 #endif /*ZIPPY*/
3620                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3621                 continue;
3622             }
3623
3624             if (looking_at(buf, &i, "Removing game * from observation") ||
3625                 looking_at(buf, &i, "no longer observing game *") ||
3626                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3627                 if (gameMode == IcsObserving &&
3628                     atoi(star_match[0]) == ics_gamenum)
3629                   {
3630                       /* icsEngineAnalyze */
3631                       if (appData.icsEngineAnalyze) {
3632                             ExitAnalyzeMode();
3633                             ModeHighlight();
3634                       }
3635                       StopClocks();
3636                       gameMode = IcsIdle;
3637                       ics_gamenum = -1;
3638                       ics_user_moved = FALSE;
3639                   }
3640                 continue;
3641             }
3642
3643             if (looking_at(buf, &i, "no longer examining game *")) {
3644                 if (gameMode == IcsExamining &&
3645                     atoi(star_match[0]) == ics_gamenum)
3646                   {
3647                       gameMode = IcsIdle;
3648                       ics_gamenum = -1;
3649                       ics_user_moved = FALSE;
3650                   }
3651                 continue;
3652             }
3653
3654             /* Advance leftover_start past any newlines we find,
3655                so only partial lines can get reparsed */
3656             if (looking_at(buf, &i, "\n")) {
3657                 prevColor = curColor;
3658                 if (curColor != ColorNormal) {
3659                     if (oldi > next_out) {
3660                         SendToPlayer(&buf[next_out], oldi - next_out);
3661                         next_out = oldi;
3662                     }
3663                     Colorize(ColorNormal, FALSE);
3664                     curColor = ColorNormal;
3665                 }
3666                 if (started == STARTED_BOARD) {
3667                     started = STARTED_NONE;
3668                     parse[parse_pos] = NULLCHAR;
3669                     ParseBoard12(parse);
3670                     ics_user_moved = 0;
3671
3672                     /* Send premove here */
3673                     if (appData.premove) {
3674                       char str[MSG_SIZ];
3675                       if (currentMove == 0 &&
3676                           gameMode == IcsPlayingWhite &&
3677                           appData.premoveWhite) {
3678                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3679                         if (appData.debugMode)
3680                           fprintf(debugFP, "Sending premove:\n");
3681                         SendToICS(str);
3682                       } else if (currentMove == 1 &&
3683                                  gameMode == IcsPlayingBlack &&
3684                                  appData.premoveBlack) {
3685                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3686                         if (appData.debugMode)
3687                           fprintf(debugFP, "Sending premove:\n");
3688                         SendToICS(str);
3689                       } else if (gotPremove) {
3690                         gotPremove = 0;
3691                         ClearPremoveHighlights();
3692                         if (appData.debugMode)
3693                           fprintf(debugFP, "Sending premove:\n");
3694                           UserMoveEvent(premoveFromX, premoveFromY,
3695                                         premoveToX, premoveToY,
3696                                         premovePromoChar);
3697                       }
3698                     }
3699
3700                     /* Usually suppress following prompt */
3701                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3702                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3703                         if (looking_at(buf, &i, "*% ")) {
3704                             savingComment = FALSE;
3705                             suppressKibitz = 0;
3706                         }
3707                     }
3708                     next_out = i;
3709                 } else if (started == STARTED_HOLDINGS) {
3710                     int gamenum;
3711                     char new_piece[MSG_SIZ];
3712                     started = STARTED_NONE;
3713                     parse[parse_pos] = NULLCHAR;
3714                     if (appData.debugMode)
3715                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3716                                                         parse, currentMove);
3717                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3718                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3719                         if (gameInfo.variant == VariantNormal) {
3720                           /* [HGM] We seem to switch variant during a game!
3721                            * Presumably no holdings were displayed, so we have
3722                            * to move the position two files to the right to
3723                            * create room for them!
3724                            */
3725                           VariantClass newVariant;
3726                           switch(gameInfo.boardWidth) { // base guess on board width
3727                                 case 9:  newVariant = VariantShogi; break;
3728                                 case 10: newVariant = VariantGreat; break;
3729                                 default: newVariant = VariantCrazyhouse; break;
3730                           }
3731                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3732                           /* Get a move list just to see the header, which
3733                              will tell us whether this is really bug or zh */
3734                           if (ics_getting_history == H_FALSE) {
3735                             ics_getting_history = H_REQUESTED;
3736                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3737                             SendToICS(str);
3738                           }
3739                         }
3740                         new_piece[0] = NULLCHAR;
3741                         sscanf(parse, "game %d white [%s black [%s <- %s",
3742                                &gamenum, white_holding, black_holding,
3743                                new_piece);
3744                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3745                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3746                         /* [HGM] copy holdings to board holdings area */
3747                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3748                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3749                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3750 #if ZIPPY
3751                         if (appData.zippyPlay && first.initDone) {
3752                             ZippyHoldings(white_holding, black_holding,
3753                                           new_piece);
3754                         }
3755 #endif /*ZIPPY*/
3756                         if (tinyLayout || smallLayout) {
3757                             char wh[16], bh[16];
3758                             PackHolding(wh, white_holding);
3759                             PackHolding(bh, black_holding);
3760                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3761                                     gameInfo.white, gameInfo.black);
3762                         } else {
3763                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3764                                     gameInfo.white, white_holding,
3765                                     gameInfo.black, black_holding);
3766                         }
3767                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3768                         DrawPosition(FALSE, boards[currentMove]);
3769                         DisplayTitle(str);
3770                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3771                         sscanf(parse, "game %d white [%s black [%s <- %s",
3772                                &gamenum, white_holding, black_holding,
3773                                new_piece);
3774                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3775                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3776                         /* [HGM] copy holdings to partner-board holdings area */
3777                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3778                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3779                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3780                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3781                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3782                       }
3783                     }
3784                     /* Suppress following prompt */
3785                     if (looking_at(buf, &i, "*% ")) {
3786                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3787                         savingComment = FALSE;
3788                         suppressKibitz = 0;
3789                     }
3790                     next_out = i;
3791                 }
3792                 continue;
3793             }
3794
3795             i++;                /* skip unparsed character and loop back */
3796         }
3797
3798         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3799 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3800 //          SendToPlayer(&buf[next_out], i - next_out);
3801             started != STARTED_HOLDINGS && leftover_start > next_out) {
3802             SendToPlayer(&buf[next_out], leftover_start - next_out);
3803             next_out = i;
3804         }
3805
3806         leftover_len = buf_len - leftover_start;
3807         /* if buffer ends with something we couldn't parse,
3808            reparse it after appending the next read */
3809
3810     } else if (count == 0) {
3811         RemoveInputSource(isr);
3812         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3813     } else {
3814         DisplayFatalError(_("Error reading from ICS"), error, 1);
3815     }
3816 }
3817
3818
3819 /* Board style 12 looks like this:
3820
3821    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3822
3823  * The "<12> " is stripped before it gets to this routine.  The two
3824  * trailing 0's (flip state and clock ticking) are later addition, and
3825  * some chess servers may not have them, or may have only the first.
3826  * Additional trailing fields may be added in the future.
3827  */
3828
3829 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3830
3831 #define RELATION_OBSERVING_PLAYED    0
3832 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3833 #define RELATION_PLAYING_MYMOVE      1
3834 #define RELATION_PLAYING_NOTMYMOVE  -1
3835 #define RELATION_EXAMINING           2
3836 #define RELATION_ISOLATED_BOARD     -3
3837 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3838
3839 void
3840 ParseBoard12(string)
3841      char *string;
3842 {
3843     GameMode newGameMode;
3844     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3845     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3846     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3847     char to_play, board_chars[200];
3848     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3849     char black[32], white[32];
3850     Board board;
3851     int prevMove = currentMove;
3852     int ticking = 2;
3853     ChessMove moveType;
3854     int fromX, fromY, toX, toY;
3855     char promoChar;
3856     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3857     char *bookHit = NULL; // [HGM] book
3858     Boolean weird = FALSE, reqFlag = FALSE;
3859
3860     fromX = fromY = toX = toY = -1;
3861
3862     newGame = FALSE;
3863
3864     if (appData.debugMode)
3865       fprintf(debugFP, _("Parsing board: %s\n"), string);
3866
3867     move_str[0] = NULLCHAR;
3868     elapsed_time[0] = NULLCHAR;
3869     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3870         int  i = 0, j;
3871         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3872             if(string[i] == ' ') { ranks++; files = 0; }
3873             else files++;
3874             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3875             i++;
3876         }
3877         for(j = 0; j <i; j++) board_chars[j] = string[j];
3878         board_chars[i] = '\0';
3879         string += i + 1;
3880     }
3881     n = sscanf(string, PATTERN, &to_play, &double_push,
3882                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3883                &gamenum, white, black, &relation, &basetime, &increment,
3884                &white_stren, &black_stren, &white_time, &black_time,
3885                &moveNum, str, elapsed_time, move_str, &ics_flip,
3886                &ticking);
3887
3888     if (n < 21) {
3889         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3890         DisplayError(str, 0);
3891         return;
3892     }
3893
3894     /* Convert the move number to internal form */
3895     moveNum = (moveNum - 1) * 2;
3896     if (to_play == 'B') moveNum++;
3897     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3898       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3899                         0, 1);
3900       return;
3901     }
3902
3903     switch (relation) {
3904       case RELATION_OBSERVING_PLAYED:
3905       case RELATION_OBSERVING_STATIC:
3906         if (gamenum == -1) {
3907             /* Old ICC buglet */
3908             relation = RELATION_OBSERVING_STATIC;
3909         }
3910         newGameMode = IcsObserving;
3911         break;
3912       case RELATION_PLAYING_MYMOVE:
3913       case RELATION_PLAYING_NOTMYMOVE:
3914         newGameMode =
3915           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3916             IcsPlayingWhite : IcsPlayingBlack;
3917         break;
3918       case RELATION_EXAMINING:
3919         newGameMode = IcsExamining;
3920         break;
3921       case RELATION_ISOLATED_BOARD:
3922       default:
3923         /* Just display this board.  If user was doing something else,
3924            we will forget about it until the next board comes. */
3925         newGameMode = IcsIdle;
3926         break;
3927       case RELATION_STARTING_POSITION:
3928         newGameMode = gameMode;
3929         break;
3930     }
3931
3932     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3933          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3934       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3935       char *toSqr;
3936       for (k = 0; k < ranks; k++) {
3937         for (j = 0; j < files; j++)
3938           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3939         if(gameInfo.holdingsWidth > 1) {
3940              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3941              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3942         }
3943       }
3944       CopyBoard(partnerBoard, board);
3945       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3946         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3947         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3948       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3949       if(toSqr = strchr(str, '-')) {
3950         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3951         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3952       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3953       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3954       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3955       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3956       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3957       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3958                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3959       DisplayMessage(partnerStatus, "");
3960         partnerBoardValid = TRUE;
3961       return;
3962     }
3963
3964     /* Modify behavior for initial board display on move listing
3965        of wild games.
3966        */
3967     switch (ics_getting_history) {
3968       case H_FALSE:
3969       case H_REQUESTED:
3970         break;
3971       case H_GOT_REQ_HEADER:
3972       case H_GOT_UNREQ_HEADER:
3973         /* This is the initial position of the current game */
3974         gamenum = ics_gamenum;
3975         moveNum = 0;            /* old ICS bug workaround */
3976         if (to_play == 'B') {
3977           startedFromSetupPosition = TRUE;
3978           blackPlaysFirst = TRUE;
3979           moveNum = 1;
3980           if (forwardMostMove == 0) forwardMostMove = 1;
3981           if (backwardMostMove == 0) backwardMostMove = 1;
3982           if (currentMove == 0) currentMove = 1;
3983         }
3984         newGameMode = gameMode;
3985         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3986         break;
3987       case H_GOT_UNWANTED_HEADER:
3988         /* This is an initial board that we don't want */
3989         return;
3990       case H_GETTING_MOVES:
3991         /* Should not happen */
3992         DisplayError(_("Error gathering move list: extra board"), 0);
3993         ics_getting_history = H_FALSE;
3994         return;
3995     }
3996
3997    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3998                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
3999      /* [HGM] We seem to have switched variant unexpectedly
4000       * Try to guess new variant from board size
4001       */
4002           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4003           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4004           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4005           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4006           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4007           if(!weird) newVariant = VariantNormal;
4008           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4009           /* Get a move list just to see the header, which
4010              will tell us whether this is really bug or zh */
4011           if (ics_getting_history == H_FALSE) {
4012             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4013             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4014             SendToICS(str);
4015           }
4016     }
4017
4018     /* Take action if this is the first board of a new game, or of a
4019        different game than is currently being displayed.  */
4020     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4021         relation == RELATION_ISOLATED_BOARD) {
4022
4023         /* Forget the old game and get the history (if any) of the new one */
4024         if (gameMode != BeginningOfGame) {
4025           Reset(TRUE, TRUE);
4026         }
4027         newGame = TRUE;
4028         if (appData.autoRaiseBoard) BoardToTop();
4029         prevMove = -3;
4030         if (gamenum == -1) {
4031             newGameMode = IcsIdle;
4032         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4033                    appData.getMoveList && !reqFlag) {
4034             /* Need to get game history */
4035             ics_getting_history = H_REQUESTED;
4036             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4037             SendToICS(str);
4038         }
4039
4040         /* Initially flip the board to have black on the bottom if playing
4041            black or if the ICS flip flag is set, but let the user change
4042            it with the Flip View button. */
4043         flipView = appData.autoFlipView ?
4044           (newGameMode == IcsPlayingBlack) || ics_flip :
4045           appData.flipView;
4046
4047         /* Done with values from previous mode; copy in new ones */
4048         gameMode = newGameMode;
4049         ModeHighlight();
4050         ics_gamenum = gamenum;
4051         if (gamenum == gs_gamenum) {
4052             int klen = strlen(gs_kind);
4053             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4054             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4055             gameInfo.event = StrSave(str);
4056         } else {
4057             gameInfo.event = StrSave("ICS game");
4058         }
4059         gameInfo.site = StrSave(appData.icsHost);
4060         gameInfo.date = PGNDate();
4061         gameInfo.round = StrSave("-");
4062         gameInfo.white = StrSave(white);
4063         gameInfo.black = StrSave(black);
4064         timeControl = basetime * 60 * 1000;
4065         timeControl_2 = 0;
4066         timeIncrement = increment * 1000;
4067         movesPerSession = 0;
4068         gameInfo.timeControl = TimeControlTagValue();
4069         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4070   if (appData.debugMode) {
4071     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4072     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4073     setbuf(debugFP, NULL);
4074   }
4075
4076         gameInfo.outOfBook = NULL;
4077
4078         /* Do we have the ratings? */
4079         if (strcmp(player1Name, white) == 0 &&
4080             strcmp(player2Name, black) == 0) {
4081             if (appData.debugMode)
4082               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4083                       player1Rating, player2Rating);
4084             gameInfo.whiteRating = player1Rating;
4085             gameInfo.blackRating = player2Rating;
4086         } else if (strcmp(player2Name, white) == 0 &&
4087                    strcmp(player1Name, black) == 0) {
4088             if (appData.debugMode)
4089               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4090                       player2Rating, player1Rating);
4091             gameInfo.whiteRating = player2Rating;
4092             gameInfo.blackRating = player1Rating;
4093         }
4094         player1Name[0] = player2Name[0] = NULLCHAR;
4095
4096         /* Silence shouts if requested */
4097         if (appData.quietPlay &&
4098             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4099             SendToICS(ics_prefix);
4100             SendToICS("set shout 0\n");
4101         }
4102     }
4103
4104     /* Deal with midgame name changes */
4105     if (!newGame) {
4106         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4107             if (gameInfo.white) free(gameInfo.white);
4108             gameInfo.white = StrSave(white);
4109         }
4110         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4111             if (gameInfo.black) free(gameInfo.black);
4112             gameInfo.black = StrSave(black);
4113         }
4114     }
4115
4116     /* Throw away game result if anything actually changes in examine mode */
4117     if (gameMode == IcsExamining && !newGame) {
4118         gameInfo.result = GameUnfinished;
4119         if (gameInfo.resultDetails != NULL) {
4120             free(gameInfo.resultDetails);
4121             gameInfo.resultDetails = NULL;
4122         }
4123     }
4124
4125     /* In pausing && IcsExamining mode, we ignore boards coming
4126        in if they are in a different variation than we are. */
4127     if (pauseExamInvalid) return;
4128     if (pausing && gameMode == IcsExamining) {
4129         if (moveNum <= pauseExamForwardMostMove) {
4130             pauseExamInvalid = TRUE;
4131             forwardMostMove = pauseExamForwardMostMove;
4132             return;
4133         }
4134     }
4135
4136   if (appData.debugMode) {
4137     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4138   }
4139     /* Parse the board */
4140     for (k = 0; k < ranks; k++) {
4141       for (j = 0; j < files; j++)
4142         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4143       if(gameInfo.holdingsWidth > 1) {
4144            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4145            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4146       }
4147     }
4148     CopyBoard(boards[moveNum], board);
4149     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4150     if (moveNum == 0) {
4151         startedFromSetupPosition =
4152           !CompareBoards(board, initialPosition);
4153         if(startedFromSetupPosition)
4154             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4155     }
4156
4157     /* [HGM] Set castling rights. Take the outermost Rooks,
4158        to make it also work for FRC opening positions. Note that board12
4159        is really defective for later FRC positions, as it has no way to
4160        indicate which Rook can castle if they are on the same side of King.
4161        For the initial position we grant rights to the outermost Rooks,
4162        and remember thos rights, and we then copy them on positions
4163        later in an FRC game. This means WB might not recognize castlings with
4164        Rooks that have moved back to their original position as illegal,
4165        but in ICS mode that is not its job anyway.
4166     */
4167     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4168     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4169
4170         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4171             if(board[0][i] == WhiteRook) j = i;
4172         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4173         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4174             if(board[0][i] == WhiteRook) j = i;
4175         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4176         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4177             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4178         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4179         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4180             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4181         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4182
4183         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4184         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4185             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4186         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4187             if(board[BOARD_HEIGHT-1][k] == bKing)
4188                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4189         if(gameInfo.variant == VariantTwoKings) {
4190             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4191             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4192             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4193         }
4194     } else { int r;
4195         r = boards[moveNum][CASTLING][0] = initialRights[0];
4196         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4197         r = boards[moveNum][CASTLING][1] = initialRights[1];
4198         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4199         r = boards[moveNum][CASTLING][3] = initialRights[3];
4200         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4201         r = boards[moveNum][CASTLING][4] = initialRights[4];
4202         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4203         /* wildcastle kludge: always assume King has rights */
4204         r = boards[moveNum][CASTLING][2] = initialRights[2];
4205         r = boards[moveNum][CASTLING][5] = initialRights[5];
4206     }
4207     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4208     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4209
4210
4211     if (ics_getting_history == H_GOT_REQ_HEADER ||
4212         ics_getting_history == H_GOT_UNREQ_HEADER) {
4213         /* This was an initial position from a move list, not
4214            the current position */
4215         return;
4216     }
4217
4218     /* Update currentMove and known move number limits */
4219     newMove = newGame || moveNum > forwardMostMove;
4220
4221     if (newGame) {
4222         forwardMostMove = backwardMostMove = currentMove = moveNum;
4223         if (gameMode == IcsExamining && moveNum == 0) {
4224           /* Workaround for ICS limitation: we are not told the wild
4225              type when starting to examine a game.  But if we ask for
4226              the move list, the move list header will tell us */
4227             ics_getting_history = H_REQUESTED;
4228             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4229             SendToICS(str);
4230         }
4231     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4232                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4233 #if ZIPPY
4234         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4235         /* [HGM] applied this also to an engine that is silently watching        */
4236         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4237             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4238             gameInfo.variant == currentlyInitializedVariant) {
4239           takeback = forwardMostMove - moveNum;
4240           for (i = 0; i < takeback; i++) {
4241             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4242             SendToProgram("undo\n", &first);
4243           }
4244         }
4245 #endif
4246
4247         forwardMostMove = moveNum;
4248         if (!pausing || currentMove > forwardMostMove)
4249           currentMove = forwardMostMove;
4250     } else {
4251         /* New part of history that is not contiguous with old part */
4252         if (pausing && gameMode == IcsExamining) {
4253             pauseExamInvalid = TRUE;
4254             forwardMostMove = pauseExamForwardMostMove;
4255             return;
4256         }
4257         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4258 #if ZIPPY
4259             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4260                 // [HGM] when we will receive the move list we now request, it will be
4261                 // fed to the engine from the first move on. So if the engine is not
4262                 // in the initial position now, bring it there.
4263                 InitChessProgram(&first, 0);
4264             }
4265 #endif
4266             ics_getting_history = H_REQUESTED;
4267             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4268             SendToICS(str);
4269         }
4270         forwardMostMove = backwardMostMove = currentMove = moveNum;
4271     }
4272
4273     /* Update the clocks */
4274     if (strchr(elapsed_time, '.')) {
4275       /* Time is in ms */
4276       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4277       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4278     } else {
4279       /* Time is in seconds */
4280       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4281       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4282     }
4283
4284
4285 #if ZIPPY
4286     if (appData.zippyPlay && newGame &&
4287         gameMode != IcsObserving && gameMode != IcsIdle &&
4288         gameMode != IcsExamining)
4289       ZippyFirstBoard(moveNum, basetime, increment);
4290 #endif
4291
4292     /* Put the move on the move list, first converting
4293        to canonical algebraic form. */
4294     if (moveNum > 0) {
4295   if (appData.debugMode) {
4296     if (appData.debugMode) { int f = forwardMostMove;
4297         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4298                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4299                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4300     }
4301     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4302     fprintf(debugFP, "moveNum = %d\n", moveNum);
4303     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4304     setbuf(debugFP, NULL);
4305   }
4306         if (moveNum <= backwardMostMove) {
4307             /* We don't know what the board looked like before
4308                this move.  Punt. */
4309           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4310             strcat(parseList[moveNum - 1], " ");
4311             strcat(parseList[moveNum - 1], elapsed_time);
4312             moveList[moveNum - 1][0] = NULLCHAR;
4313         } else if (strcmp(move_str, "none") == 0) {
4314             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4315             /* Again, we don't know what the board looked like;
4316                this is really the start of the game. */
4317             parseList[moveNum - 1][0] = NULLCHAR;
4318             moveList[moveNum - 1][0] = NULLCHAR;
4319             backwardMostMove = moveNum;
4320             startedFromSetupPosition = TRUE;
4321             fromX = fromY = toX = toY = -1;
4322         } else {
4323           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4324           //                 So we parse the long-algebraic move string in stead of the SAN move
4325           int valid; char buf[MSG_SIZ], *prom;
4326
4327           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4328                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4329           // str looks something like "Q/a1-a2"; kill the slash
4330           if(str[1] == '/')
4331             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4332           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4333           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4334                 strcat(buf, prom); // long move lacks promo specification!
4335           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4336                 if(appData.debugMode)
4337                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4338                 safeStrCpy(move_str, buf, MSG_SIZ);
4339           }
4340           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4341                                 &fromX, &fromY, &toX, &toY, &promoChar)
4342                || ParseOneMove(buf, moveNum - 1, &moveType,
4343                                 &fromX, &fromY, &toX, &toY, &promoChar);
4344           // end of long SAN patch
4345           if (valid) {
4346             (void) CoordsToAlgebraic(boards[moveNum - 1],
4347                                      PosFlags(moveNum - 1),
4348                                      fromY, fromX, toY, toX, promoChar,
4349                                      parseList[moveNum-1]);
4350             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4351               case MT_NONE:
4352               case MT_STALEMATE:
4353               default:
4354                 break;
4355               case MT_CHECK:
4356                 if(gameInfo.variant != VariantShogi)
4357                     strcat(parseList[moveNum - 1], "+");
4358                 break;
4359               case MT_CHECKMATE:
4360               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4361                 strcat(parseList[moveNum - 1], "#");
4362                 break;
4363             }
4364             strcat(parseList[moveNum - 1], " ");
4365             strcat(parseList[moveNum - 1], elapsed_time);
4366             /* currentMoveString is set as a side-effect of ParseOneMove */
4367             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4368             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4369             strcat(moveList[moveNum - 1], "\n");
4370
4371             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4372               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4373                 ChessSquare old, new = boards[moveNum][k][j];
4374                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4375                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4376                   if(old == new) continue;
4377                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4378                   else if(new == WhiteWazir || new == BlackWazir) {
4379                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4380                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4381                       else boards[moveNum][k][j] = old; // preserve type of Gold
4382                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4383                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4384               }
4385           } else {
4386             /* Move from ICS was illegal!?  Punt. */
4387             if (appData.debugMode) {
4388               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4389               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4390             }
4391             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4392             strcat(parseList[moveNum - 1], " ");
4393             strcat(parseList[moveNum - 1], elapsed_time);
4394             moveList[moveNum - 1][0] = NULLCHAR;
4395             fromX = fromY = toX = toY = -1;
4396           }
4397         }
4398   if (appData.debugMode) {
4399     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4400     setbuf(debugFP, NULL);
4401   }
4402
4403 #if ZIPPY
4404         /* Send move to chess program (BEFORE animating it). */
4405         if (appData.zippyPlay && !newGame && newMove &&
4406            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4407
4408             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4409                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4410                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4411                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4412                             move_str);
4413                     DisplayError(str, 0);
4414                 } else {
4415                     if (first.sendTime) {
4416                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4417                     }
4418                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4419                     if (firstMove && !bookHit) {
4420                         firstMove = FALSE;
4421                         if (first.useColors) {
4422                           SendToProgram(gameMode == IcsPlayingWhite ?
4423                                         "white\ngo\n" :
4424                                         "black\ngo\n", &first);
4425                         } else {
4426                           SendToProgram("go\n", &first);
4427                         }
4428                         first.maybeThinking = TRUE;
4429                     }
4430                 }
4431             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4432               if (moveList[moveNum - 1][0] == NULLCHAR) {
4433                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4434                 DisplayError(str, 0);
4435               } else {
4436                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4437                 SendMoveToProgram(moveNum - 1, &first);
4438               }
4439             }
4440         }
4441 #endif
4442     }
4443
4444     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4445         /* If move comes from a remote source, animate it.  If it
4446            isn't remote, it will have already been animated. */
4447         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4448             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4449         }
4450         if (!pausing && appData.highlightLastMove) {
4451             SetHighlights(fromX, fromY, toX, toY);
4452         }
4453     }
4454
4455     /* Start the clocks */
4456     whiteFlag = blackFlag = FALSE;
4457     appData.clockMode = !(basetime == 0 && increment == 0);
4458     if (ticking == 0) {
4459       ics_clock_paused = TRUE;
4460       StopClocks();
4461     } else if (ticking == 1) {
4462       ics_clock_paused = FALSE;
4463     }
4464     if (gameMode == IcsIdle ||
4465         relation == RELATION_OBSERVING_STATIC ||
4466         relation == RELATION_EXAMINING ||
4467         ics_clock_paused)
4468       DisplayBothClocks();
4469     else
4470       StartClocks();
4471
4472     /* Display opponents and material strengths */
4473     if (gameInfo.variant != VariantBughouse &&
4474         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4475         if (tinyLayout || smallLayout) {
4476             if(gameInfo.variant == VariantNormal)
4477               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4478                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4479                     basetime, increment);
4480             else
4481               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4482                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4483                     basetime, increment, (int) gameInfo.variant);
4484         } else {
4485             if(gameInfo.variant == VariantNormal)
4486               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4487                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4488                     basetime, increment);
4489             else
4490               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment, VariantName(gameInfo.variant));
4493         }
4494         DisplayTitle(str);
4495   if (appData.debugMode) {
4496     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4497   }
4498     }
4499
4500
4501     /* Display the board */
4502     if (!pausing && !appData.noGUI) {
4503
4504       if (appData.premove)
4505           if (!gotPremove ||
4506              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4507              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4508               ClearPremoveHighlights();
4509
4510       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4511         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4512       DrawPosition(j, boards[currentMove]);
4513
4514       DisplayMove(moveNum - 1);
4515       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4516             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4517               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4518         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4519       }
4520     }
4521
4522     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4523 #if ZIPPY
4524     if(bookHit) { // [HGM] book: simulate book reply
4525         static char bookMove[MSG_SIZ]; // a bit generous?
4526
4527         programStats.nodes = programStats.depth = programStats.time =
4528         programStats.score = programStats.got_only_move = 0;
4529         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4530
4531         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4532         strcat(bookMove, bookHit);
4533         HandleMachineMove(bookMove, &first);
4534     }
4535 #endif
4536 }
4537
4538 void
4539 GetMoveListEvent()
4540 {
4541     char buf[MSG_SIZ];
4542     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4543         ics_getting_history = H_REQUESTED;
4544         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4545         SendToICS(buf);
4546     }
4547 }
4548
4549 void
4550 AnalysisPeriodicEvent(force)
4551      int force;
4552 {
4553     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4554          && !force) || !appData.periodicUpdates)
4555       return;
4556
4557     /* Send . command to Crafty to collect stats */
4558     SendToProgram(".\n", &first);
4559
4560     /* Don't send another until we get a response (this makes
4561        us stop sending to old Crafty's which don't understand
4562        the "." command (sending illegal cmds resets node count & time,
4563        which looks bad)) */
4564     programStats.ok_to_send = 0;
4565 }
4566
4567 void ics_update_width(new_width)
4568         int new_width;
4569 {
4570         ics_printf("set width %d\n", new_width);
4571 }
4572
4573 void
4574 SendMoveToProgram(moveNum, cps)
4575      int moveNum;
4576      ChessProgramState *cps;
4577 {
4578     char buf[MSG_SIZ];
4579
4580     if (cps->useUsermove) {
4581       SendToProgram("usermove ", cps);
4582     }
4583     if (cps->useSAN) {
4584       char *space;
4585       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4586         int len = space - parseList[moveNum];
4587         memcpy(buf, parseList[moveNum], len);
4588         buf[len++] = '\n';
4589         buf[len] = NULLCHAR;
4590       } else {
4591         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4592       }
4593       SendToProgram(buf, cps);
4594     } else {
4595       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4596         AlphaRank(moveList[moveNum], 4);
4597         SendToProgram(moveList[moveNum], cps);
4598         AlphaRank(moveList[moveNum], 4); // and back
4599       } else
4600       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4601        * the engine. It would be nice to have a better way to identify castle
4602        * moves here. */
4603       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4604                                                                          && cps->useOOCastle) {
4605         int fromX = moveList[moveNum][0] - AAA;
4606         int fromY = moveList[moveNum][1] - ONE;
4607         int toX = moveList[moveNum][2] - AAA;
4608         int toY = moveList[moveNum][3] - ONE;
4609         if((boards[moveNum][fromY][fromX] == WhiteKing
4610             && boards[moveNum][toY][toX] == WhiteRook)
4611            || (boards[moveNum][fromY][fromX] == BlackKing
4612                && boards[moveNum][toY][toX] == BlackRook)) {
4613           if(toX > fromX) SendToProgram("O-O\n", cps);
4614           else SendToProgram("O-O-O\n", cps);
4615         }
4616         else SendToProgram(moveList[moveNum], cps);
4617       }
4618       else SendToProgram(moveList[moveNum], cps);
4619       /* End of additions by Tord */
4620     }
4621
4622     /* [HGM] setting up the opening has brought engine in force mode! */
4623     /*       Send 'go' if we are in a mode where machine should play. */
4624     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4625         (gameMode == TwoMachinesPlay   ||
4626 #if ZIPPY
4627          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4628 #endif
4629          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4630         SendToProgram("go\n", cps);
4631   if (appData.debugMode) {
4632     fprintf(debugFP, "(extra)\n");
4633   }
4634     }
4635     setboardSpoiledMachineBlack = 0;
4636 }
4637
4638 void
4639 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4640      ChessMove moveType;
4641      int fromX, fromY, toX, toY;
4642      char promoChar;
4643 {
4644     char user_move[MSG_SIZ];
4645
4646     switch (moveType) {
4647       default:
4648         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4649                 (int)moveType, fromX, fromY, toX, toY);
4650         DisplayError(user_move + strlen("say "), 0);
4651         break;
4652       case WhiteKingSideCastle:
4653       case BlackKingSideCastle:
4654       case WhiteQueenSideCastleWild:
4655       case BlackQueenSideCastleWild:
4656       /* PUSH Fabien */
4657       case WhiteHSideCastleFR:
4658       case BlackHSideCastleFR:
4659       /* POP Fabien */
4660         snprintf(user_move, MSG_SIZ, "o-o\n");
4661         break;
4662       case WhiteQueenSideCastle:
4663       case BlackQueenSideCastle:
4664       case WhiteKingSideCastleWild:
4665       case BlackKingSideCastleWild:
4666       /* PUSH Fabien */
4667       case WhiteASideCastleFR:
4668       case BlackASideCastleFR:
4669       /* POP Fabien */
4670         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4671         break;
4672       case WhiteNonPromotion:
4673       case BlackNonPromotion:
4674         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4675         break;
4676       case WhitePromotion:
4677       case BlackPromotion:
4678         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4679           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4680                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4681                 PieceToChar(WhiteFerz));
4682         else if(gameInfo.variant == VariantGreat)
4683           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4684                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4685                 PieceToChar(WhiteMan));
4686         else
4687           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4688                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4689                 promoChar);
4690         break;
4691       case WhiteDrop:
4692       case BlackDrop:
4693       drop:
4694         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4695                  ToUpper(PieceToChar((ChessSquare) fromX)),
4696                  AAA + toX, ONE + toY);
4697         break;
4698       case IllegalMove:  /* could be a variant we don't quite understand */
4699         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4700       case NormalMove:
4701       case WhiteCapturesEnPassant:
4702       case BlackCapturesEnPassant:
4703         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4704                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4705         break;
4706     }
4707     SendToICS(user_move);
4708     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4709         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4710 }
4711
4712 void
4713 UploadGameEvent()
4714 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4715     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4716     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4717     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4718         DisplayError("You cannot do this while you are playing or observing", 0);
4719         return;
4720     }
4721     if(gameMode != IcsExamining) { // is this ever not the case?
4722         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4723
4724         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4725           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4726         } else { // on FICS we must first go to general examine mode
4727           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4728         }
4729         if(gameInfo.variant != VariantNormal) {
4730             // try figure out wild number, as xboard names are not always valid on ICS
4731             for(i=1; i<=36; i++) {
4732               snprintf(buf, MSG_SIZ, "wild/%d", i);
4733                 if(StringToVariant(buf) == gameInfo.variant) break;
4734             }
4735             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4736             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4737             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4738         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4739         SendToICS(ics_prefix);
4740         SendToICS(buf);
4741         if(startedFromSetupPosition || backwardMostMove != 0) {
4742           fen = PositionToFEN(backwardMostMove, NULL);
4743           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4744             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4745             SendToICS(buf);
4746           } else { // FICS: everything has to set by separate bsetup commands
4747             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4748             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4749             SendToICS(buf);
4750             if(!WhiteOnMove(backwardMostMove)) {
4751                 SendToICS("bsetup tomove black\n");
4752             }
4753             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4754             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4755             SendToICS(buf);
4756             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4757             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4758             SendToICS(buf);
4759             i = boards[backwardMostMove][EP_STATUS];
4760             if(i >= 0) { // set e.p.
4761               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4762                 SendToICS(buf);
4763             }
4764             bsetup++;
4765           }
4766         }
4767       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4768             SendToICS("bsetup done\n"); // switch to normal examining.
4769     }
4770     for(i = backwardMostMove; i<last; i++) {
4771         char buf[20];
4772         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4773         SendToICS(buf);
4774     }
4775     SendToICS(ics_prefix);
4776     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4777 }
4778
4779 void
4780 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4781      int rf, ff, rt, ft;
4782      char promoChar;
4783      char move[7];
4784 {
4785     if (rf == DROP_RANK) {
4786       sprintf(move, "%c@%c%c\n",
4787                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4788     } else {
4789         if (promoChar == 'x' || promoChar == NULLCHAR) {
4790           sprintf(move, "%c%c%c%c\n",
4791                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4792         } else {
4793             sprintf(move, "%c%c%c%c%c\n",
4794                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4795         }
4796     }
4797 }
4798
4799 void
4800 ProcessICSInitScript(f)
4801      FILE *f;
4802 {
4803     char buf[MSG_SIZ];
4804
4805     while (fgets(buf, MSG_SIZ, f)) {
4806         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4807     }
4808
4809     fclose(f);
4810 }
4811
4812
4813 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4814 void
4815 AlphaRank(char *move, int n)
4816 {
4817 //    char *p = move, c; int x, y;
4818
4819     if (appData.debugMode) {
4820         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4821     }
4822
4823     if(move[1]=='*' &&
4824        move[2]>='0' && move[2]<='9' &&
4825        move[3]>='a' && move[3]<='x'    ) {
4826         move[1] = '@';
4827         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4828         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4829     } else
4830     if(move[0]>='0' && move[0]<='9' &&
4831        move[1]>='a' && move[1]<='x' &&
4832        move[2]>='0' && move[2]<='9' &&
4833        move[3]>='a' && move[3]<='x'    ) {
4834         /* input move, Shogi -> normal */
4835         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4836         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4837         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4838         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4839     } else
4840     if(move[1]=='@' &&
4841        move[3]>='0' && move[3]<='9' &&
4842        move[2]>='a' && move[2]<='x'    ) {
4843         move[1] = '*';
4844         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4845         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4846     } else
4847     if(
4848        move[0]>='a' && move[0]<='x' &&
4849        move[3]>='0' && move[3]<='9' &&
4850        move[2]>='a' && move[2]<='x'    ) {
4851          /* output move, normal -> Shogi */
4852         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4853         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4854         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4855         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4856         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4857     }
4858     if (appData.debugMode) {
4859         fprintf(debugFP, "   out = '%s'\n", move);
4860     }
4861 }
4862
4863 char yy_textstr[8000];
4864
4865 /* Parser for moves from gnuchess, ICS, or user typein box */
4866 Boolean
4867 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4868      char *move;
4869      int moveNum;
4870      ChessMove *moveType;
4871      int *fromX, *fromY, *toX, *toY;
4872      char *promoChar;
4873 {
4874     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4875
4876     switch (*moveType) {
4877       case WhitePromotion:
4878       case BlackPromotion:
4879       case WhiteNonPromotion:
4880       case BlackNonPromotion:
4881       case NormalMove:
4882       case WhiteCapturesEnPassant:
4883       case BlackCapturesEnPassant:
4884       case WhiteKingSideCastle:
4885       case WhiteQueenSideCastle:
4886       case BlackKingSideCastle:
4887       case BlackQueenSideCastle:
4888       case WhiteKingSideCastleWild:
4889       case WhiteQueenSideCastleWild:
4890       case BlackKingSideCastleWild:
4891       case BlackQueenSideCastleWild:
4892       /* Code added by Tord: */
4893       case WhiteHSideCastleFR:
4894       case WhiteASideCastleFR:
4895       case BlackHSideCastleFR:
4896       case BlackASideCastleFR:
4897       /* End of code added by Tord */
4898       case IllegalMove:         /* bug or odd chess variant */
4899         *fromX = currentMoveString[0] - AAA;
4900         *fromY = currentMoveString[1] - ONE;
4901         *toX = currentMoveString[2] - AAA;
4902         *toY = currentMoveString[3] - ONE;
4903         *promoChar = currentMoveString[4];
4904         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4905             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4906     if (appData.debugMode) {
4907         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4908     }
4909             *fromX = *fromY = *toX = *toY = 0;
4910             return FALSE;
4911         }
4912         if (appData.testLegality) {
4913           return (*moveType != IllegalMove);
4914         } else {
4915           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4916                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4917         }
4918
4919       case WhiteDrop:
4920       case BlackDrop:
4921         *fromX = *moveType == WhiteDrop ?
4922           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4923           (int) CharToPiece(ToLower(currentMoveString[0]));
4924         *fromY = DROP_RANK;
4925         *toX = currentMoveString[2] - AAA;
4926         *toY = currentMoveString[3] - ONE;
4927         *promoChar = NULLCHAR;
4928         return TRUE;
4929
4930       case AmbiguousMove:
4931       case ImpossibleMove:
4932       case EndOfFile:
4933       case ElapsedTime:
4934       case Comment:
4935       case PGNTag:
4936       case NAG:
4937       case WhiteWins:
4938       case BlackWins:
4939       case GameIsDrawn:
4940       default:
4941     if (appData.debugMode) {
4942         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4943     }
4944         /* bug? */
4945         *fromX = *fromY = *toX = *toY = 0;
4946         *promoChar = NULLCHAR;
4947         return FALSE;
4948     }
4949 }
4950
4951
4952 void
4953 ParsePV(char *pv, Boolean storeComments)
4954 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4955   int fromX, fromY, toX, toY; char promoChar;
4956   ChessMove moveType;
4957   Boolean valid;
4958   int nr = 0;
4959
4960   endPV = forwardMostMove;
4961   do {
4962     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4963     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4964     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4965 if(appData.debugMode){
4966 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4967 }
4968     if(!valid && nr == 0 &&
4969        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4970         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4971         // Hande case where played move is different from leading PV move
4972         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4973         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4974         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4975         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4976           endPV += 2; // if position different, keep this
4977           moveList[endPV-1][0] = fromX + AAA;
4978           moveList[endPV-1][1] = fromY + ONE;
4979           moveList[endPV-1][2] = toX + AAA;
4980           moveList[endPV-1][3] = toY + ONE;
4981           parseList[endPV-1][0] = NULLCHAR;
4982           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4983         }
4984       }
4985     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4986     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4987     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4988     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4989         valid++; // allow comments in PV
4990         continue;
4991     }
4992     nr++;
4993     if(endPV+1 > framePtr) break; // no space, truncate
4994     if(!valid) break;
4995     endPV++;
4996     CopyBoard(boards[endPV], boards[endPV-1]);
4997     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4998     moveList[endPV-1][0] = fromX + AAA;
4999     moveList[endPV-1][1] = fromY + ONE;
5000     moveList[endPV-1][2] = toX + AAA;
5001     moveList[endPV-1][3] = toY + ONE;
5002     moveList[endPV-1][4] = promoChar;
5003     moveList[endPV-1][5] = NULLCHAR;
5004     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5005     if(storeComments)
5006         CoordsToAlgebraic(boards[endPV - 1],
5007                              PosFlags(endPV - 1),
5008                              fromY, fromX, toY, toX, promoChar,
5009                              parseList[endPV - 1]);
5010     else
5011         parseList[endPV-1][0] = NULLCHAR;
5012   } while(valid);
5013   currentMove = endPV;
5014   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5015   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5016                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5017   DrawPosition(TRUE, boards[currentMove]);
5018 }
5019
5020 static int lastX, lastY;
5021
5022 Boolean
5023 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5024 {
5025         int startPV;
5026         char *p;
5027
5028         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5029         lastX = x; lastY = y;
5030         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5031         startPV = index;
5032         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5033         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5034         index = startPV;
5035         do{ while(buf[index] && buf[index] != '\n') index++;
5036         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5037         buf[index] = 0;
5038         ParsePV(buf+startPV, FALSE);
5039         *start = startPV; *end = index-1;
5040         return TRUE;
5041 }
5042
5043 Boolean
5044 LoadPV(int x, int y)
5045 { // called on right mouse click to load PV
5046   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5047   lastX = x; lastY = y;
5048   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5049   return TRUE;
5050 }
5051
5052 void
5053 UnLoadPV()
5054 {
5055   if(endPV < 0) return;
5056   endPV = -1;
5057   currentMove = forwardMostMove;
5058   ClearPremoveHighlights();
5059   DrawPosition(TRUE, boards[currentMove]);
5060 }
5061
5062 void
5063 MovePV(int x, int y, int h)
5064 { // step through PV based on mouse coordinates (called on mouse move)
5065   int margin = h>>3, step = 0;
5066
5067   if(endPV < 0) return;
5068   // we must somehow check if right button is still down (might be released off board!)
5069   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5070   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5071   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5072   if(!step) return;
5073   lastX = x; lastY = y;
5074   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5075   currentMove += step;
5076   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5077   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5078                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5079   DrawPosition(FALSE, boards[currentMove]);
5080 }
5081
5082
5083 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5084 // All positions will have equal probability, but the current method will not provide a unique
5085 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5086 #define DARK 1
5087 #define LITE 2
5088 #define ANY 3
5089
5090 int squaresLeft[4];
5091 int piecesLeft[(int)BlackPawn];
5092 int seed, nrOfShuffles;
5093
5094 void GetPositionNumber()
5095 {       // sets global variable seed
5096         int i;
5097
5098         seed = appData.defaultFrcPosition;
5099         if(seed < 0) { // randomize based on time for negative FRC position numbers
5100                 for(i=0; i<50; i++) seed += random();
5101                 seed = random() ^ random() >> 8 ^ random() << 8;
5102                 if(seed<0) seed = -seed;
5103         }
5104 }
5105
5106 int put(Board board, int pieceType, int rank, int n, int shade)
5107 // put the piece on the (n-1)-th empty squares of the given shade
5108 {
5109         int i;
5110
5111         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5112                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5113                         board[rank][i] = (ChessSquare) pieceType;
5114                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5115                         squaresLeft[ANY]--;
5116                         piecesLeft[pieceType]--;
5117                         return i;
5118                 }
5119         }
5120         return -1;
5121 }
5122
5123
5124 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5125 // calculate where the next piece goes, (any empty square), and put it there
5126 {
5127         int i;
5128
5129         i = seed % squaresLeft[shade];
5130         nrOfShuffles *= squaresLeft[shade];
5131         seed /= squaresLeft[shade];
5132         put(board, pieceType, rank, i, shade);
5133 }
5134
5135 void AddTwoPieces(Board board, int pieceType, int rank)
5136 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5137 {
5138         int i, n=squaresLeft[ANY], j=n-1, k;
5139
5140         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5141         i = seed % k;  // pick one
5142         nrOfShuffles *= k;
5143         seed /= k;
5144         while(i >= j) i -= j--;
5145         j = n - 1 - j; i += j;
5146         put(board, pieceType, rank, j, ANY);
5147         put(board, pieceType, rank, i, ANY);
5148 }
5149
5150 void SetUpShuffle(Board board, int number)
5151 {
5152         int i, p, first=1;
5153
5154         GetPositionNumber(); nrOfShuffles = 1;
5155
5156         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5157         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5158         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5159
5160         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5161
5162         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5163             p = (int) board[0][i];
5164             if(p < (int) BlackPawn) piecesLeft[p] ++;
5165             board[0][i] = EmptySquare;
5166         }
5167
5168         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5169             // shuffles restricted to allow normal castling put KRR first
5170             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5171                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5172             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5173                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5174             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5175                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5176             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5177                 put(board, WhiteRook, 0, 0, ANY);
5178             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5179         }
5180
5181         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5182             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5183             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5184                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5185                 while(piecesLeft[p] >= 2) {
5186                     AddOnePiece(board, p, 0, LITE);
5187                     AddOnePiece(board, p, 0, DARK);
5188                 }
5189                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5190             }
5191
5192         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5193             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5194             // but we leave King and Rooks for last, to possibly obey FRC restriction
5195             if(p == (int)WhiteRook) continue;
5196             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5197             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5198         }
5199
5200         // now everything is placed, except perhaps King (Unicorn) and Rooks
5201
5202         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5203             // Last King gets castling rights
5204             while(piecesLeft[(int)WhiteUnicorn]) {
5205                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5206                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5207             }
5208
5209             while(piecesLeft[(int)WhiteKing]) {
5210                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5211                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5212             }
5213
5214
5215         } else {
5216             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5217             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5218         }
5219
5220         // Only Rooks can be left; simply place them all
5221         while(piecesLeft[(int)WhiteRook]) {
5222                 i = put(board, WhiteRook, 0, 0, ANY);
5223                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5224                         if(first) {
5225                                 first=0;
5226                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5227                         }
5228                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5229                 }
5230         }
5231         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5232             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5233         }
5234
5235         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5236 }
5237
5238 int SetCharTable( char *table, const char * map )
5239 /* [HGM] moved here from winboard.c because of its general usefulness */
5240 /*       Basically a safe strcpy that uses the last character as King */
5241 {
5242     int result = FALSE; int NrPieces;
5243
5244     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5245                     && NrPieces >= 12 && !(NrPieces&1)) {
5246         int i; /* [HGM] Accept even length from 12 to 34 */
5247
5248         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5249         for( i=0; i<NrPieces/2-1; i++ ) {
5250             table[i] = map[i];
5251             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5252         }
5253         table[(int) WhiteKing]  = map[NrPieces/2-1];
5254         table[(int) BlackKing]  = map[NrPieces-1];
5255
5256         result = TRUE;
5257     }
5258
5259     return result;
5260 }
5261
5262 void Prelude(Board board)
5263 {       // [HGM] superchess: random selection of exo-pieces
5264         int i, j, k; ChessSquare p;
5265         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5266
5267         GetPositionNumber(); // use FRC position number
5268
5269         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5270             SetCharTable(pieceToChar, appData.pieceToCharTable);
5271             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5272                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5273         }
5274
5275         j = seed%4;                 seed /= 4;
5276         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5277         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5278         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5279         j = seed%3 + (seed%3 >= j); seed /= 3;
5280         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5281         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5282         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5283         j = seed%3;                 seed /= 3;
5284         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5285         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5286         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5287         j = seed%2 + (seed%2 >= j); seed /= 2;
5288         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5289         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5290         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5291         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5292         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5293         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5294         put(board, exoPieces[0],    0, 0, ANY);
5295         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5296 }
5297
5298 void
5299 InitPosition(redraw)
5300      int redraw;
5301 {
5302     ChessSquare (* pieces)[BOARD_FILES];
5303     int i, j, pawnRow, overrule,
5304     oldx = gameInfo.boardWidth,
5305     oldy = gameInfo.boardHeight,
5306     oldh = gameInfo.holdingsWidth;
5307     static int oldv;
5308
5309     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5310
5311     /* [AS] Initialize pv info list [HGM] and game status */
5312     {
5313         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5314             pvInfoList[i].depth = 0;
5315             boards[i][EP_STATUS] = EP_NONE;
5316             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5317         }
5318
5319         initialRulePlies = 0; /* 50-move counter start */
5320
5321         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5322         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5323     }
5324
5325
5326     /* [HGM] logic here is completely changed. In stead of full positions */
5327     /* the initialized data only consist of the two backranks. The switch */
5328     /* selects which one we will use, which is than copied to the Board   */
5329     /* initialPosition, which for the rest is initialized by Pawns and    */
5330     /* empty squares. This initial position is then copied to boards[0],  */
5331     /* possibly after shuffling, so that it remains available.            */
5332
5333     gameInfo.holdingsWidth = 0; /* default board sizes */
5334     gameInfo.boardWidth    = 8;
5335     gameInfo.boardHeight   = 8;
5336     gameInfo.holdingsSize  = 0;
5337     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5338     for(i=0; i<BOARD_FILES-2; i++)
5339       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5340     initialPosition[EP_STATUS] = EP_NONE;
5341     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5342     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5343          SetCharTable(pieceNickName, appData.pieceNickNames);
5344     else SetCharTable(pieceNickName, "............");
5345     pieces = FIDEArray;
5346
5347     switch (gameInfo.variant) {
5348     case VariantFischeRandom:
5349       shuffleOpenings = TRUE;
5350     default:
5351       break;
5352     case VariantShatranj:
5353       pieces = ShatranjArray;
5354       nrCastlingRights = 0;
5355       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5356       break;
5357     case VariantMakruk:
5358       pieces = makrukArray;
5359       nrCastlingRights = 0;
5360       startedFromSetupPosition = TRUE;
5361       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5362       break;
5363     case VariantTwoKings:
5364       pieces = twoKingsArray;
5365       break;
5366     case VariantCapaRandom:
5367       shuffleOpenings = TRUE;
5368     case VariantCapablanca:
5369       pieces = CapablancaArray;
5370       gameInfo.boardWidth = 10;
5371       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5372       break;
5373     case VariantGothic:
5374       pieces = GothicArray;
5375       gameInfo.boardWidth = 10;
5376       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5377       break;
5378     case VariantSChess:
5379       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5380       gameInfo.holdingsSize = 7;
5381       break;
5382     case VariantJanus:
5383       pieces = JanusArray;
5384       gameInfo.boardWidth = 10;
5385       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5386       nrCastlingRights = 6;
5387         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5388         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5389         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5390         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5391         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5392         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5393       break;
5394     case VariantFalcon:
5395       pieces = FalconArray;
5396       gameInfo.boardWidth = 10;
5397       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5398       break;
5399     case VariantXiangqi:
5400       pieces = XiangqiArray;
5401       gameInfo.boardWidth  = 9;
5402       gameInfo.boardHeight = 10;
5403       nrCastlingRights = 0;
5404       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5405       break;
5406     case VariantShogi:
5407       pieces = ShogiArray;
5408       gameInfo.boardWidth  = 9;
5409       gameInfo.boardHeight = 9;
5410       gameInfo.holdingsSize = 7;
5411       nrCastlingRights = 0;
5412       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5413       break;
5414     case VariantCourier:
5415       pieces = CourierArray;
5416       gameInfo.boardWidth  = 12;
5417       nrCastlingRights = 0;
5418       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5419       break;
5420     case VariantKnightmate:
5421       pieces = KnightmateArray;
5422       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5423       break;
5424     case VariantSpartan:
5425       pieces = SpartanArray;
5426       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5427       break;
5428     case VariantFairy:
5429       pieces = fairyArray;
5430       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5431       break;
5432     case VariantGreat:
5433       pieces = GreatArray;
5434       gameInfo.boardWidth = 10;
5435       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5436       gameInfo.holdingsSize = 8;
5437       break;
5438     case VariantSuper:
5439       pieces = FIDEArray;
5440       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5441       gameInfo.holdingsSize = 8;
5442       startedFromSetupPosition = TRUE;
5443       break;
5444     case VariantCrazyhouse:
5445     case VariantBughouse:
5446       pieces = FIDEArray;
5447       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5448       gameInfo.holdingsSize = 5;
5449       break;
5450     case VariantWildCastle:
5451       pieces = FIDEArray;
5452       /* !!?shuffle with kings guaranteed to be on d or e file */
5453       shuffleOpenings = 1;
5454       break;
5455     case VariantNoCastle:
5456       pieces = FIDEArray;
5457       nrCastlingRights = 0;
5458       /* !!?unconstrained back-rank shuffle */
5459       shuffleOpenings = 1;
5460       break;
5461     }
5462
5463     overrule = 0;
5464     if(appData.NrFiles >= 0) {
5465         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5466         gameInfo.boardWidth = appData.NrFiles;
5467     }
5468     if(appData.NrRanks >= 0) {
5469         gameInfo.boardHeight = appData.NrRanks;
5470     }
5471     if(appData.holdingsSize >= 0) {
5472         i = appData.holdingsSize;
5473         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5474         gameInfo.holdingsSize = i;
5475     }
5476     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5477     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5478         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5479
5480     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5481     if(pawnRow < 1) pawnRow = 1;
5482     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5483
5484     /* User pieceToChar list overrules defaults */
5485     if(appData.pieceToCharTable != NULL)
5486         SetCharTable(pieceToChar, appData.pieceToCharTable);
5487
5488     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5489
5490         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5491             s = (ChessSquare) 0; /* account holding counts in guard band */
5492         for( i=0; i<BOARD_HEIGHT; i++ )
5493             initialPosition[i][j] = s;
5494
5495         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5496         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5497         initialPosition[pawnRow][j] = WhitePawn;
5498         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5499         if(gameInfo.variant == VariantXiangqi) {
5500             if(j&1) {
5501                 initialPosition[pawnRow][j] =
5502                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5503                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5504                    initialPosition[2][j] = WhiteCannon;
5505                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5506                 }
5507             }
5508         }
5509         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5510     }
5511     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5512
5513             j=BOARD_LEFT+1;
5514             initialPosition[1][j] = WhiteBishop;
5515             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5516             j=BOARD_RGHT-2;
5517             initialPosition[1][j] = WhiteRook;
5518             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5519     }
5520
5521     if( nrCastlingRights == -1) {
5522         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5523         /*       This sets default castling rights from none to normal corners   */
5524         /* Variants with other castling rights must set them themselves above    */
5525         nrCastlingRights = 6;
5526
5527         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5528         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5529         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5530         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5531         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5532         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5533      }
5534
5535      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5536      if(gameInfo.variant == VariantGreat) { // promotion commoners
5537         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5538         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5539         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5540         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5541      }
5542      if( gameInfo.variant == VariantSChess ) {
5543       initialPosition[1][0] = BlackMarshall;
5544       initialPosition[2][0] = BlackAngel;
5545       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5546       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5547       initialPosition[1][1] = initialPosition[2][1] = 
5548       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5549      }
5550   if (appData.debugMode) {
5551     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5552   }
5553     if(shuffleOpenings) {
5554         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5555         startedFromSetupPosition = TRUE;
5556     }
5557     if(startedFromPositionFile) {
5558       /* [HGM] loadPos: use PositionFile for every new game */
5559       CopyBoard(initialPosition, filePosition);
5560       for(i=0; i<nrCastlingRights; i++)
5561           initialRights[i] = filePosition[CASTLING][i];
5562       startedFromSetupPosition = TRUE;
5563     }
5564
5565     CopyBoard(boards[0], initialPosition);
5566
5567     if(oldx != gameInfo.boardWidth ||
5568        oldy != gameInfo.boardHeight ||
5569        oldv != gameInfo.variant ||
5570        oldh != gameInfo.holdingsWidth
5571                                          )
5572             InitDrawingSizes(-2 ,0);
5573
5574     oldv = gameInfo.variant;
5575     if (redraw)
5576       DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 SendBoard(cps, moveNum)
5581      ChessProgramState *cps;
5582      int moveNum;
5583 {
5584     char message[MSG_SIZ];
5585
5586     if (cps->useSetboard) {
5587       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5588       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5589       SendToProgram(message, cps);
5590       free(fen);
5591
5592     } else {
5593       ChessSquare *bp;
5594       int i, j;
5595       /* Kludge to set black to move, avoiding the troublesome and now
5596        * deprecated "black" command.
5597        */
5598       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5599         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5600
5601       SendToProgram("edit\n", cps);
5602       SendToProgram("#\n", cps);
5603       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5604         bp = &boards[moveNum][i][BOARD_LEFT];
5605         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5606           if ((int) *bp < (int) BlackPawn) {
5607             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5608                     AAA + j, ONE + i);
5609             if(message[0] == '+' || message[0] == '~') {
5610               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5611                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5612                         AAA + j, ONE + i);
5613             }
5614             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5615                 message[1] = BOARD_RGHT   - 1 - j + '1';
5616                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5617             }
5618             SendToProgram(message, cps);
5619           }
5620         }
5621       }
5622
5623       SendToProgram("c\n", cps);
5624       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5625         bp = &boards[moveNum][i][BOARD_LEFT];
5626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5627           if (((int) *bp != (int) EmptySquare)
5628               && ((int) *bp >= (int) BlackPawn)) {
5629             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5630                     AAA + j, ONE + i);
5631             if(message[0] == '+' || message[0] == '~') {
5632               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5633                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5634                         AAA + j, ONE + i);
5635             }
5636             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5637                 message[1] = BOARD_RGHT   - 1 - j + '1';
5638                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5639             }
5640             SendToProgram(message, cps);
5641           }
5642         }
5643       }
5644
5645       SendToProgram(".\n", cps);
5646     }
5647     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5648 }
5649
5650 static int autoQueen; // [HGM] oneclick
5651
5652 int
5653 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5654 {
5655     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5656     /* [HGM] add Shogi promotions */
5657     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5658     ChessSquare piece;
5659     ChessMove moveType;
5660     Boolean premove;
5661
5662     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5663     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5664
5665     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5666       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5667         return FALSE;
5668
5669     piece = boards[currentMove][fromY][fromX];
5670     if(gameInfo.variant == VariantShogi) {
5671         promotionZoneSize = BOARD_HEIGHT/3;
5672         highestPromotingPiece = (int)WhiteFerz;
5673     } else if(gameInfo.variant == VariantMakruk) {
5674         promotionZoneSize = 3;
5675     }
5676
5677     // Treat Lance as Pawn when it is not representing Amazon
5678     if(gameInfo.variant != VariantSuper) {
5679         if(piece == WhiteLance) piece = WhitePawn; else
5680         if(piece == BlackLance) piece = BlackPawn;
5681     }
5682
5683     // next weed out all moves that do not touch the promotion zone at all
5684     if((int)piece >= BlackPawn) {
5685         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5686              return FALSE;
5687         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5688     } else {
5689         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5690            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5691     }
5692
5693     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5694
5695     // weed out mandatory Shogi promotions
5696     if(gameInfo.variant == VariantShogi) {
5697         if(piece >= BlackPawn) {
5698             if(toY == 0 && piece == BlackPawn ||
5699                toY == 0 && piece == BlackQueen ||
5700                toY <= 1 && piece == BlackKnight) {
5701                 *promoChoice = '+';
5702                 return FALSE;
5703             }
5704         } else {
5705             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5706                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5707                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5708                 *promoChoice = '+';
5709                 return FALSE;
5710             }
5711         }
5712     }
5713
5714     // weed out obviously illegal Pawn moves
5715     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5716         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5717         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5718         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5719         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5720         // note we are not allowed to test for valid (non-)capture, due to premove
5721     }
5722
5723     // we either have a choice what to promote to, or (in Shogi) whether to promote
5724     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5725         *promoChoice = PieceToChar(BlackFerz);  // no choice
5726         return FALSE;
5727     }
5728     // no sense asking what we must promote to if it is going to explode...
5729     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5730         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5731         return FALSE;
5732     }
5733     if(autoQueen) { // predetermined
5734         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5735              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5736         else *promoChoice = PieceToChar(BlackQueen);
5737         return FALSE;
5738     }
5739
5740     // suppress promotion popup on illegal moves that are not premoves
5741     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5742               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5743     if(appData.testLegality && !premove) {
5744         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5745                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5746         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5747             return FALSE;
5748     }
5749
5750     return TRUE;
5751 }
5752
5753 int
5754 InPalace(row, column)
5755      int row, column;
5756 {   /* [HGM] for Xiangqi */
5757     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5758          column < (BOARD_WIDTH + 4)/2 &&
5759          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5760     return FALSE;
5761 }
5762
5763 int
5764 PieceForSquare (x, y)
5765      int x;
5766      int y;
5767 {
5768   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5769      return -1;
5770   else
5771      return boards[currentMove][y][x];
5772 }
5773
5774 int
5775 OKToStartUserMove(x, y)
5776      int x, y;
5777 {
5778     ChessSquare from_piece;
5779     int white_piece;
5780
5781     if (matchMode) return FALSE;
5782     if (gameMode == EditPosition) return TRUE;
5783
5784     if (x >= 0 && y >= 0)
5785       from_piece = boards[currentMove][y][x];
5786     else
5787       from_piece = EmptySquare;
5788
5789     if (from_piece == EmptySquare) return FALSE;
5790
5791     white_piece = (int)from_piece >= (int)WhitePawn &&
5792       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5793
5794     switch (gameMode) {
5795       case PlayFromGameFile:
5796       case AnalyzeFile:
5797       case TwoMachinesPlay:
5798       case EndOfGame:
5799         return FALSE;
5800
5801       case IcsObserving:
5802       case IcsIdle:
5803         return FALSE;
5804
5805       case MachinePlaysWhite:
5806       case IcsPlayingBlack:
5807         if (appData.zippyPlay) return FALSE;
5808         if (white_piece) {
5809             DisplayMoveError(_("You are playing Black"));
5810             return FALSE;
5811         }
5812         break;
5813
5814       case MachinePlaysBlack:
5815       case IcsPlayingWhite:
5816         if (appData.zippyPlay) return FALSE;
5817         if (!white_piece) {
5818             DisplayMoveError(_("You are playing White"));
5819             return FALSE;
5820         }
5821         break;
5822
5823       case EditGame:
5824         if (!white_piece && WhiteOnMove(currentMove)) {
5825             DisplayMoveError(_("It is White's turn"));
5826             return FALSE;
5827         }
5828         if (white_piece && !WhiteOnMove(currentMove)) {
5829             DisplayMoveError(_("It is Black's turn"));
5830             return FALSE;
5831         }
5832         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5833             /* Editing correspondence game history */
5834             /* Could disallow this or prompt for confirmation */
5835             cmailOldMove = -1;
5836         }
5837         break;
5838
5839       case BeginningOfGame:
5840         if (appData.icsActive) return FALSE;
5841         if (!appData.noChessProgram) {
5842             if (!white_piece) {
5843                 DisplayMoveError(_("You are playing White"));
5844                 return FALSE;
5845             }
5846         }
5847         break;
5848
5849       case Training:
5850         if (!white_piece && WhiteOnMove(currentMove)) {
5851             DisplayMoveError(_("It is White's turn"));
5852             return FALSE;
5853         }
5854         if (white_piece && !WhiteOnMove(currentMove)) {
5855             DisplayMoveError(_("It is Black's turn"));
5856             return FALSE;
5857         }
5858         break;
5859
5860       default:
5861       case IcsExamining:
5862         break;
5863     }
5864     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5865         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5866         && gameMode != AnalyzeFile && gameMode != Training) {
5867         DisplayMoveError(_("Displayed position is not current"));
5868         return FALSE;
5869     }
5870     return TRUE;
5871 }
5872
5873 Boolean
5874 OnlyMove(int *x, int *y, Boolean captures) {
5875     DisambiguateClosure cl;
5876     if (appData.zippyPlay) return FALSE;
5877     switch(gameMode) {
5878       case MachinePlaysBlack:
5879       case IcsPlayingWhite:
5880       case BeginningOfGame:
5881         if(!WhiteOnMove(currentMove)) return FALSE;
5882         break;
5883       case MachinePlaysWhite:
5884       case IcsPlayingBlack:
5885         if(WhiteOnMove(currentMove)) return FALSE;
5886         break;
5887       case EditGame:
5888         break;
5889       default:
5890         return FALSE;
5891     }
5892     cl.pieceIn = EmptySquare;
5893     cl.rfIn = *y;
5894     cl.ffIn = *x;
5895     cl.rtIn = -1;
5896     cl.ftIn = -1;
5897     cl.promoCharIn = NULLCHAR;
5898     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5899     if( cl.kind == NormalMove ||
5900         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5901         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5902         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5903       fromX = cl.ff;
5904       fromY = cl.rf;
5905       *x = cl.ft;
5906       *y = cl.rt;
5907       return TRUE;
5908     }
5909     if(cl.kind != ImpossibleMove) return FALSE;
5910     cl.pieceIn = EmptySquare;
5911     cl.rfIn = -1;
5912     cl.ffIn = -1;
5913     cl.rtIn = *y;
5914     cl.ftIn = *x;
5915     cl.promoCharIn = NULLCHAR;
5916     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5917     if( cl.kind == NormalMove ||
5918         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5919         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5920         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5921       fromX = cl.ff;
5922       fromY = cl.rf;
5923       *x = cl.ft;
5924       *y = cl.rt;
5925       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5926       return TRUE;
5927     }
5928     return FALSE;
5929 }
5930
5931 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5932 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5933 int lastLoadGameUseList = FALSE;
5934 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5935 ChessMove lastLoadGameStart = EndOfFile;
5936
5937 void
5938 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5939      int fromX, fromY, toX, toY;
5940      int promoChar;
5941 {
5942     ChessMove moveType;
5943     ChessSquare pdown, pup;
5944
5945     /* Check if the user is playing in turn.  This is complicated because we
5946        let the user "pick up" a piece before it is his turn.  So the piece he
5947        tried to pick up may have been captured by the time he puts it down!
5948        Therefore we use the color the user is supposed to be playing in this
5949        test, not the color of the piece that is currently on the starting
5950        square---except in EditGame mode, where the user is playing both
5951        sides; fortunately there the capture race can't happen.  (It can
5952        now happen in IcsExamining mode, but that's just too bad.  The user
5953        will get a somewhat confusing message in that case.)
5954        */
5955
5956     switch (gameMode) {
5957       case PlayFromGameFile:
5958       case AnalyzeFile:
5959       case TwoMachinesPlay:
5960       case EndOfGame:
5961       case IcsObserving:
5962       case IcsIdle:
5963         /* We switched into a game mode where moves are not accepted,
5964            perhaps while the mouse button was down. */
5965         return;
5966
5967       case MachinePlaysWhite:
5968         /* User is moving for Black */
5969         if (WhiteOnMove(currentMove)) {
5970             DisplayMoveError(_("It is White's turn"));
5971             return;
5972         }
5973         break;
5974
5975       case MachinePlaysBlack:
5976         /* User is moving for White */
5977         if (!WhiteOnMove(currentMove)) {
5978             DisplayMoveError(_("It is Black's turn"));
5979             return;
5980         }
5981         break;
5982
5983       case EditGame:
5984       case IcsExamining:
5985       case BeginningOfGame:
5986       case AnalyzeMode:
5987       case Training:
5988         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
5989         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5990             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5991             /* User is moving for Black */
5992             if (WhiteOnMove(currentMove)) {
5993                 DisplayMoveError(_("It is White's turn"));
5994                 return;
5995             }
5996         } else {
5997             /* User is moving for White */
5998             if (!WhiteOnMove(currentMove)) {
5999                 DisplayMoveError(_("It is Black's turn"));
6000                 return;
6001             }
6002         }
6003         break;
6004
6005       case IcsPlayingBlack:
6006         /* User is moving for Black */
6007         if (WhiteOnMove(currentMove)) {
6008             if (!appData.premove) {
6009                 DisplayMoveError(_("It is White's turn"));
6010             } else if (toX >= 0 && toY >= 0) {
6011                 premoveToX = toX;
6012                 premoveToY = toY;
6013                 premoveFromX = fromX;
6014                 premoveFromY = fromY;
6015                 premovePromoChar = promoChar;
6016                 gotPremove = 1;
6017                 if (appData.debugMode)
6018                     fprintf(debugFP, "Got premove: fromX %d,"
6019                             "fromY %d, toX %d, toY %d\n",
6020                             fromX, fromY, toX, toY);
6021             }
6022             return;
6023         }
6024         break;
6025
6026       case IcsPlayingWhite:
6027         /* User is moving for White */
6028         if (!WhiteOnMove(currentMove)) {
6029             if (!appData.premove) {
6030                 DisplayMoveError(_("It is Black's turn"));
6031             } else if (toX >= 0 && toY >= 0) {
6032                 premoveToX = toX;
6033                 premoveToY = toY;
6034                 premoveFromX = fromX;
6035                 premoveFromY = fromY;
6036                 premovePromoChar = promoChar;
6037                 gotPremove = 1;
6038                 if (appData.debugMode)
6039                     fprintf(debugFP, "Got premove: fromX %d,"
6040                             "fromY %d, toX %d, toY %d\n",
6041                             fromX, fromY, toX, toY);
6042             }
6043             return;
6044         }
6045         break;
6046
6047       default:
6048         break;
6049
6050       case EditPosition:
6051         /* EditPosition, empty square, or different color piece;
6052            click-click move is possible */
6053         if (toX == -2 || toY == -2) {
6054             boards[0][fromY][fromX] = EmptySquare;
6055             DrawPosition(FALSE, boards[currentMove]);
6056             return;
6057         } else if (toX >= 0 && toY >= 0) {
6058             boards[0][toY][toX] = boards[0][fromY][fromX];
6059             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6060                 if(boards[0][fromY][0] != EmptySquare) {
6061                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6062                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6063                 }
6064             } else
6065             if(fromX == BOARD_RGHT+1) {
6066                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6067                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6068                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6069                 }
6070             } else
6071             boards[0][fromY][fromX] = EmptySquare;
6072             DrawPosition(FALSE, boards[currentMove]);
6073             return;
6074         }
6075         return;
6076     }
6077
6078     if(toX < 0 || toY < 0) return;
6079     pdown = boards[currentMove][fromY][fromX];
6080     pup = boards[currentMove][toY][toX];
6081
6082     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6083     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6084          if( pup != EmptySquare ) return;
6085          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6086            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6087                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6088            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6089            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6090            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6091            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6092          fromY = DROP_RANK;
6093     }
6094
6095     /* [HGM] always test for legality, to get promotion info */
6096     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6097                                          fromY, fromX, toY, toX, promoChar);
6098     /* [HGM] but possibly ignore an IllegalMove result */
6099     if (appData.testLegality) {
6100         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6101             DisplayMoveError(_("Illegal move"));
6102             return;
6103         }
6104     }
6105
6106     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6107 }
6108
6109 /* Common tail of UserMoveEvent and DropMenuEvent */
6110 int
6111 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6112      ChessMove moveType;
6113      int fromX, fromY, toX, toY;
6114      /*char*/int promoChar;
6115 {
6116     char *bookHit = 0;
6117
6118     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6119         // [HGM] superchess: suppress promotions to non-available piece
6120         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6121         if(WhiteOnMove(currentMove)) {
6122             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6123         } else {
6124             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6125         }
6126     }
6127
6128     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6129        move type in caller when we know the move is a legal promotion */
6130     if(moveType == NormalMove && promoChar)
6131         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6132
6133     /* [HGM] <popupFix> The following if has been moved here from
6134        UserMoveEvent(). Because it seemed to belong here (why not allow
6135        piece drops in training games?), and because it can only be
6136        performed after it is known to what we promote. */
6137     if (gameMode == Training) {
6138       /* compare the move played on the board to the next move in the
6139        * game. If they match, display the move and the opponent's response.
6140        * If they don't match, display an error message.
6141        */
6142       int saveAnimate;
6143       Board testBoard;
6144       CopyBoard(testBoard, boards[currentMove]);
6145       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6146
6147       if (CompareBoards(testBoard, boards[currentMove+1])) {
6148         ForwardInner(currentMove+1);
6149
6150         /* Autoplay the opponent's response.
6151          * if appData.animate was TRUE when Training mode was entered,
6152          * the response will be animated.
6153          */
6154         saveAnimate = appData.animate;
6155         appData.animate = animateTraining;
6156         ForwardInner(currentMove+1);
6157         appData.animate = saveAnimate;
6158
6159         /* check for the end of the game */
6160         if (currentMove >= forwardMostMove) {
6161           gameMode = PlayFromGameFile;
6162           ModeHighlight();
6163           SetTrainingModeOff();
6164           DisplayInformation(_("End of game"));
6165         }
6166       } else {
6167         DisplayError(_("Incorrect move"), 0);
6168       }
6169       return 1;
6170     }
6171
6172   /* Ok, now we know that the move is good, so we can kill
6173      the previous line in Analysis Mode */
6174   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6175                                 && currentMove < forwardMostMove) {
6176     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6177     else forwardMostMove = currentMove;
6178   }
6179
6180   /* If we need the chess program but it's dead, restart it */
6181   ResurrectChessProgram();
6182
6183   /* A user move restarts a paused game*/
6184   if (pausing)
6185     PauseEvent();
6186
6187   thinkOutput[0] = NULLCHAR;
6188
6189   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6190
6191   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6192
6193   if (gameMode == BeginningOfGame) {
6194     if (appData.noChessProgram) {
6195       gameMode = EditGame;
6196       SetGameInfo();
6197     } else {
6198       char buf[MSG_SIZ];
6199       gameMode = MachinePlaysBlack;
6200       StartClocks();
6201       SetGameInfo();
6202       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6203       DisplayTitle(buf);
6204       if (first.sendName) {
6205         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6206         SendToProgram(buf, &first);
6207       }
6208       StartClocks();
6209     }
6210     ModeHighlight();
6211   }
6212
6213   /* Relay move to ICS or chess engine */
6214   if (appData.icsActive) {
6215     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6216         gameMode == IcsExamining) {
6217       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6218         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6219         SendToICS("draw ");
6220         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6221       }
6222       // also send plain move, in case ICS does not understand atomic claims
6223       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6224       ics_user_moved = 1;
6225     }
6226   } else {
6227     if (first.sendTime && (gameMode == BeginningOfGame ||
6228                            gameMode == MachinePlaysWhite ||
6229                            gameMode == MachinePlaysBlack)) {
6230       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6231     }
6232     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6233          // [HGM] book: if program might be playing, let it use book
6234         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6235         first.maybeThinking = TRUE;
6236     } else SendMoveToProgram(forwardMostMove-1, &first);
6237     if (currentMove == cmailOldMove + 1) {
6238       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6239     }
6240   }
6241
6242   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6243
6244   switch (gameMode) {
6245   case EditGame:
6246     if(appData.testLegality)
6247     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6248     case MT_NONE:
6249     case MT_CHECK:
6250       break;
6251     case MT_CHECKMATE:
6252     case MT_STAINMATE:
6253       if (WhiteOnMove(currentMove)) {
6254         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6255       } else {
6256         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6257       }
6258       break;
6259     case MT_STALEMATE:
6260       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6261       break;
6262     }
6263     break;
6264
6265   case MachinePlaysBlack:
6266   case MachinePlaysWhite:
6267     /* disable certain menu options while machine is thinking */
6268     SetMachineThinkingEnables();
6269     break;
6270
6271   default:
6272     break;
6273   }
6274
6275   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6276
6277   if(bookHit) { // [HGM] book: simulate book reply
6278         static char bookMove[MSG_SIZ]; // a bit generous?
6279
6280         programStats.nodes = programStats.depth = programStats.time =
6281         programStats.score = programStats.got_only_move = 0;
6282         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6283
6284         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6285         strcat(bookMove, bookHit);
6286         HandleMachineMove(bookMove, &first);
6287   }
6288   return 1;
6289 }
6290
6291 void
6292 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6293      Board board;
6294      int flags;
6295      ChessMove kind;
6296      int rf, ff, rt, ft;
6297      VOIDSTAR closure;
6298 {
6299     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6300     Markers *m = (Markers *) closure;
6301     if(rf == fromY && ff == fromX)
6302         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6303                          || kind == WhiteCapturesEnPassant
6304                          || kind == BlackCapturesEnPassant);
6305     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6306 }
6307
6308 void
6309 MarkTargetSquares(int clear)
6310 {
6311   int x, y;
6312   if(!appData.markers || !appData.highlightDragging ||
6313      !appData.testLegality || gameMode == EditPosition) return;
6314   if(clear) {
6315     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6316   } else {
6317     int capt = 0;
6318     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6319     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6320       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6321       if(capt)
6322       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6323     }
6324   }
6325   DrawPosition(TRUE, NULL);
6326 }
6327
6328 int
6329 Explode(Board board, int fromX, int fromY, int toX, int toY)
6330 {
6331     if(gameInfo.variant == VariantAtomic &&
6332        (board[toY][toX] != EmptySquare ||                     // capture?
6333         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6334                          board[fromY][fromX] == BlackPawn   )
6335       )) {
6336         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6337         return TRUE;
6338     }
6339     return FALSE;
6340 }
6341
6342 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6343
6344 void LeftClick(ClickType clickType, int xPix, int yPix)
6345 {
6346     int x, y;
6347     Boolean saveAnimate;
6348     static int second = 0, promotionChoice = 0, dragging = 0;
6349     char promoChoice = NULLCHAR;
6350
6351     if(appData.seekGraph && appData.icsActive && loggedOn &&
6352         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6353         SeekGraphClick(clickType, xPix, yPix, 0);
6354         return;
6355     }
6356
6357     if (clickType == Press) ErrorPopDown();
6358     MarkTargetSquares(1);
6359
6360     x = EventToSquare(xPix, BOARD_WIDTH);
6361     y = EventToSquare(yPix, BOARD_HEIGHT);
6362     if (!flipView && y >= 0) {
6363         y = BOARD_HEIGHT - 1 - y;
6364     }
6365     if (flipView && x >= 0) {
6366         x = BOARD_WIDTH - 1 - x;
6367     }
6368
6369     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6370         if(clickType == Release) return; // ignore upclick of click-click destination
6371         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6372         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6373         if(gameInfo.holdingsWidth &&
6374                 (WhiteOnMove(currentMove)
6375                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6376                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6377             // click in right holdings, for determining promotion piece
6378             ChessSquare p = boards[currentMove][y][x];
6379             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6380             if(p != EmptySquare) {
6381                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6382                 fromX = fromY = -1;
6383                 return;
6384             }
6385         }
6386         DrawPosition(FALSE, boards[currentMove]);
6387         return;
6388     }
6389
6390     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6391     if(clickType == Press
6392             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6393               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6394               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6395         return;
6396
6397     autoQueen = appData.alwaysPromoteToQueen;
6398
6399     if (fromX == -1) {
6400       gatingPiece = EmptySquare;
6401       if (clickType != Press) {
6402         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6403             DragPieceEnd(xPix, yPix); dragging = 0;
6404             DrawPosition(FALSE, NULL);
6405         }
6406         return;
6407       }
6408       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6409             /* First square */
6410             if (OKToStartUserMove(x, y)) {
6411                 fromX = x;
6412                 fromY = y;
6413                 second = 0;
6414                 MarkTargetSquares(0);
6415                 DragPieceBegin(xPix, yPix); dragging = 1;
6416                 if (appData.highlightDragging) {
6417                     SetHighlights(x, y, -1, -1);
6418                 }
6419             }
6420             return;
6421         }
6422     }
6423
6424     /* fromX != -1 */
6425     if (clickType == Press && gameMode != EditPosition) {
6426         ChessSquare fromP;
6427         ChessSquare toP;
6428         int frc;
6429
6430         // ignore off-board to clicks
6431         if(y < 0 || x < 0) return;
6432
6433         /* Check if clicking again on the same color piece */
6434         fromP = boards[currentMove][fromY][fromX];
6435         toP = boards[currentMove][y][x];
6436         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6437         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6438              WhitePawn <= toP && toP <= WhiteKing &&
6439              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6440              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6441             (BlackPawn <= fromP && fromP <= BlackKing &&
6442              BlackPawn <= toP && toP <= BlackKing &&
6443              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6444              !(fromP == BlackKing && toP == BlackRook && frc))) {
6445             /* Clicked again on same color piece -- changed his mind */
6446             second = (x == fromX && y == fromY);
6447            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6448             if (appData.highlightDragging) {
6449                 SetHighlights(x, y, -1, -1);
6450             } else {
6451                 ClearHighlights();
6452             }
6453             if (OKToStartUserMove(x, y)) {
6454                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6455                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6456                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6457                  gatingPiece = boards[currentMove][fromY][fromX];
6458                 else gatingPiece = EmptySquare;
6459                 fromX = x;
6460                 fromY = y; dragging = 1;
6461                 MarkTargetSquares(0);
6462                 DragPieceBegin(xPix, yPix);
6463             }
6464            }
6465            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6466            second = FALSE; 
6467         }
6468         // ignore clicks on holdings
6469         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6470     }
6471
6472     if (clickType == Release && x == fromX && y == fromY) {
6473         DragPieceEnd(xPix, yPix); dragging = 0;
6474         if (appData.animateDragging) {
6475             /* Undo animation damage if any */
6476             DrawPosition(FALSE, NULL);
6477         }
6478         if (second) {
6479             /* Second up/down in same square; just abort move */
6480             second = 0;
6481             fromX = fromY = -1;
6482             gatingPiece = EmptySquare;
6483             ClearHighlights();
6484             gotPremove = 0;
6485             ClearPremoveHighlights();
6486         } else {
6487             /* First upclick in same square; start click-click mode */
6488             SetHighlights(x, y, -1, -1);
6489         }
6490         return;
6491     }
6492
6493     /* we now have a different from- and (possibly off-board) to-square */
6494     /* Completed move */
6495     toX = x;
6496     toY = y;
6497     saveAnimate = appData.animate;
6498     if (clickType == Press) {
6499         /* Finish clickclick move */
6500         if (appData.animate || appData.highlightLastMove) {
6501             SetHighlights(fromX, fromY, toX, toY);
6502         } else {
6503             ClearHighlights();
6504         }
6505     } else {
6506         /* Finish drag move */
6507         if (appData.highlightLastMove) {
6508             SetHighlights(fromX, fromY, toX, toY);
6509         } else {
6510             ClearHighlights();
6511         }
6512         DragPieceEnd(xPix, yPix); dragging = 0;
6513         /* Don't animate move and drag both */
6514         appData.animate = FALSE;
6515     }
6516
6517     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6518     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6519         ChessSquare piece = boards[currentMove][fromY][fromX];
6520         if(gameMode == EditPosition && piece != EmptySquare &&
6521            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6522             int n;
6523
6524             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6525                 n = PieceToNumber(piece - (int)BlackPawn);
6526                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6527                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6528                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6529             } else
6530             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6531                 n = PieceToNumber(piece);
6532                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6533                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6534                 boards[currentMove][n][BOARD_WIDTH-2]++;
6535             }
6536             boards[currentMove][fromY][fromX] = EmptySquare;
6537         }
6538         ClearHighlights();
6539         fromX = fromY = -1;
6540         DrawPosition(TRUE, boards[currentMove]);
6541         return;
6542     }
6543
6544     // off-board moves should not be highlighted
6545     if(x < 0 || y < 0) ClearHighlights();
6546
6547     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6548
6549     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6550         SetHighlights(fromX, fromY, toX, toY);
6551         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6552             // [HGM] super: promotion to captured piece selected from holdings
6553             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6554             promotionChoice = TRUE;
6555             // kludge follows to temporarily execute move on display, without promoting yet
6556             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6557             boards[currentMove][toY][toX] = p;
6558             DrawPosition(FALSE, boards[currentMove]);
6559             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6560             boards[currentMove][toY][toX] = q;
6561             DisplayMessage("Click in holdings to choose piece", "");
6562             return;
6563         }
6564         PromotionPopUp();
6565     } else {
6566         int oldMove = currentMove;
6567         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6568         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6569         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6570         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6571            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6572             DrawPosition(TRUE, boards[currentMove]);
6573         fromX = fromY = -1;
6574     }
6575     appData.animate = saveAnimate;
6576     if (appData.animate || appData.animateDragging) {
6577         /* Undo animation damage if needed */
6578         DrawPosition(FALSE, NULL);
6579     }
6580 }
6581
6582 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6583 {   // front-end-free part taken out of PieceMenuPopup
6584     int whichMenu; int xSqr, ySqr;
6585
6586     if(seekGraphUp) { // [HGM] seekgraph
6587         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6588         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6589         return -2;
6590     }
6591
6592     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6593          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6594         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6595         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6596         if(action == Press)   {
6597             originalFlip = flipView;
6598             flipView = !flipView; // temporarily flip board to see game from partners perspective
6599             DrawPosition(TRUE, partnerBoard);
6600             DisplayMessage(partnerStatus, "");
6601             partnerUp = TRUE;
6602         } else if(action == Release) {
6603             flipView = originalFlip;
6604             DrawPosition(TRUE, boards[currentMove]);
6605             partnerUp = FALSE;
6606         }
6607         return -2;
6608     }
6609
6610     xSqr = EventToSquare(x, BOARD_WIDTH);
6611     ySqr = EventToSquare(y, BOARD_HEIGHT);
6612     if (action == Release) UnLoadPV(); // [HGM] pv
6613     if (action != Press) return -2; // return code to be ignored
6614     switch (gameMode) {
6615       case IcsExamining:
6616         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6617       case EditPosition:
6618         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6619         if (xSqr < 0 || ySqr < 0) return -1;\r
6620         whichMenu = 0; // edit-position menu
6621         break;
6622       case IcsObserving:
6623         if(!appData.icsEngineAnalyze) return -1;
6624       case IcsPlayingWhite:
6625       case IcsPlayingBlack:
6626         if(!appData.zippyPlay) goto noZip;
6627       case AnalyzeMode:
6628       case AnalyzeFile:
6629       case MachinePlaysWhite:
6630       case MachinePlaysBlack:
6631       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6632         if (!appData.dropMenu) {
6633           LoadPV(x, y);
6634           return 2; // flag front-end to grab mouse events
6635         }
6636         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6637            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6638       case EditGame:
6639       noZip:
6640         if (xSqr < 0 || ySqr < 0) return -1;
6641         if (!appData.dropMenu || appData.testLegality &&
6642             gameInfo.variant != VariantBughouse &&
6643             gameInfo.variant != VariantCrazyhouse) return -1;
6644         whichMenu = 1; // drop menu
6645         break;
6646       default:
6647         return -1;
6648     }
6649
6650     if (((*fromX = xSqr) < 0) ||
6651         ((*fromY = ySqr) < 0)) {
6652         *fromX = *fromY = -1;
6653         return -1;
6654     }
6655     if (flipView)
6656       *fromX = BOARD_WIDTH - 1 - *fromX;
6657     else
6658       *fromY = BOARD_HEIGHT - 1 - *fromY;
6659
6660     return whichMenu;
6661 }
6662
6663 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6664 {
6665 //    char * hint = lastHint;
6666     FrontEndProgramStats stats;
6667
6668     stats.which = cps == &first ? 0 : 1;
6669     stats.depth = cpstats->depth;
6670     stats.nodes = cpstats->nodes;
6671     stats.score = cpstats->score;
6672     stats.time = cpstats->time;
6673     stats.pv = cpstats->movelist;
6674     stats.hint = lastHint;
6675     stats.an_move_index = 0;
6676     stats.an_move_count = 0;
6677
6678     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6679         stats.hint = cpstats->move_name;
6680         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6681         stats.an_move_count = cpstats->nr_moves;
6682     }
6683
6684     if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
6685
6686     SetProgramStats( &stats );
6687 }
6688
6689 void
6690 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6691 {       // count all piece types
6692         int p, f, r;
6693         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6694         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6695         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6696                 p = board[r][f];
6697                 pCnt[p]++;
6698                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6699                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6700                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6701                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6702                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6703                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6704         }
6705 }
6706
6707 int
6708 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6709 {
6710         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6711         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6712
6713         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6714         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6715         if(myPawns == 2 && nMine == 3) // KPP
6716             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6717         if(myPawns == 1 && nMine == 2) // KP
6718             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6719         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6720             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6721         if(myPawns) return FALSE;
6722         if(pCnt[WhiteRook+side])
6723             return pCnt[BlackRook-side] ||
6724                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6725                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6726                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6727         if(pCnt[WhiteCannon+side]) {
6728             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6729             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6730         }
6731         if(pCnt[WhiteKnight+side])
6732             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6733         return FALSE;
6734 }
6735
6736 int
6737 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6738 {
6739         VariantClass v = gameInfo.variant;
6740
6741         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6742         if(v == VariantShatranj) return TRUE; // always winnable through baring
6743         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6744         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6745
6746         if(v == VariantXiangqi) {
6747                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6748
6749                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6750                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6751                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6752                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6753                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6754                 if(stale) // we have at least one last-rank P plus perhaps C
6755                     return majors // KPKX
6756                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6757                 else // KCA*E*
6758                     return pCnt[WhiteFerz+side] // KCAK
6759                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6760                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6761                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6762
6763         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6764                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6765
6766                 if(nMine == 1) return FALSE; // bare King
6767                 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
6768                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6769                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6770                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6771                 if(pCnt[WhiteKnight+side])
6772                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6773                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6774                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6775                 if(nBishops)
6776                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6777                 if(pCnt[WhiteAlfil+side])
6778                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6779                 if(pCnt[WhiteWazir+side])
6780                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6781         }
6782
6783         return TRUE;
6784 }
6785
6786 int
6787 Adjudicate(ChessProgramState *cps)
6788 {       // [HGM] some adjudications useful with buggy engines
6789         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6790         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6791         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6792         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6793         int k, count = 0; static int bare = 1;
6794         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6795         Boolean canAdjudicate = !appData.icsActive;
6796
6797         // most tests only when we understand the game, i.e. legality-checking on
6798             if( appData.testLegality )
6799             {   /* [HGM] Some more adjudications for obstinate engines */
6800                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6801                 static int moveCount = 6;
6802                 ChessMove result;
6803                 char *reason = NULL;
6804
6805                 /* Count what is on board. */
6806                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6807
6808                 /* Some material-based adjudications that have to be made before stalemate test */
6809                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6810                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6811                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6812                      if(canAdjudicate && appData.checkMates) {
6813                          if(engineOpponent)
6814                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6815                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6816                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6817                          return 1;
6818                      }
6819                 }
6820
6821                 /* Bare King in Shatranj (loses) or Losers (wins) */
6822                 if( nrW == 1 || nrB == 1) {
6823                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6824                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6825                      if(canAdjudicate && appData.checkMates) {
6826                          if(engineOpponent)
6827                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6828                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6829                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6830                          return 1;
6831                      }
6832                   } else
6833                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6834                   {    /* bare King */
6835                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6836                         if(canAdjudicate && appData.checkMates) {
6837                             /* but only adjudicate if adjudication enabled */
6838                             if(engineOpponent)
6839                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6840                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6841                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6842                             return 1;
6843                         }
6844                   }
6845                 } else bare = 1;
6846
6847
6848             // don't wait for engine to announce game end if we can judge ourselves
6849             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6850               case MT_CHECK:
6851                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6852                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6853                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6854                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6855                             checkCnt++;
6856                         if(checkCnt >= 2) {
6857                             reason = "Xboard adjudication: 3rd check";
6858                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6859                             break;
6860                         }
6861                     }
6862                 }
6863               case MT_NONE:
6864               default:
6865                 break;
6866               case MT_STALEMATE:
6867               case MT_STAINMATE:
6868                 reason = "Xboard adjudication: Stalemate";
6869                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6870                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6871                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6872                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6873                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6874                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6875                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6876                                                                         EP_CHECKMATE : EP_WINS);
6877                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6878                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6879                 }
6880                 break;
6881               case MT_CHECKMATE:
6882                 reason = "Xboard adjudication: Checkmate";
6883                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6884                 break;
6885             }
6886
6887                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6888                     case EP_STALEMATE:
6889                         result = GameIsDrawn; break;
6890                     case EP_CHECKMATE:
6891                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6892                     case EP_WINS:
6893                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6894                     default:
6895                         result = EndOfFile;
6896                 }
6897                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6898                     if(engineOpponent)
6899                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6900                     GameEnds( result, reason, GE_XBOARD );
6901                     return 1;
6902                 }
6903
6904                 /* Next absolutely insufficient mating material. */
6905                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6906                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6907                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6908
6909                      /* always flag draws, for judging claims */
6910                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6911
6912                      if(canAdjudicate && appData.materialDraws) {
6913                          /* but only adjudicate them if adjudication enabled */
6914                          if(engineOpponent) {
6915                            SendToProgram("force\n", engineOpponent); // suppress reply
6916                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6917                          }
6918                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6919                          return 1;
6920                      }
6921                 }
6922
6923                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6924                 if(gameInfo.variant == VariantXiangqi ?
6925                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6926                  : nrW + nrB == 4 &&
6927                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6928                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6929                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6930                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6931                    ) ) {
6932                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6933                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6934                           if(engineOpponent) {
6935                             SendToProgram("force\n", engineOpponent); // suppress reply
6936                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6937                           }
6938                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6939                           return 1;
6940                      }
6941                 } else moveCount = 6;
6942             }
6943         if (appData.debugMode) { int i;
6944             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6945                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6946                     appData.drawRepeats);
6947             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6948               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6949
6950         }
6951
6952         // Repetition draws and 50-move rule can be applied independently of legality testing
6953
6954                 /* Check for rep-draws */
6955                 count = 0;
6956                 for(k = forwardMostMove-2;
6957                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6958                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6959                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6960                     k-=2)
6961                 {   int rights=0;
6962                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6963                         /* compare castling rights */
6964                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6965                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6966                                 rights++; /* King lost rights, while rook still had them */
6967                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6968                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6969                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6970                                    rights++; /* but at least one rook lost them */
6971                         }
6972                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6973                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6974                                 rights++;
6975                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6976                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6977                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6978                                    rights++;
6979                         }
6980                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6981                             && appData.drawRepeats > 1) {
6982                              /* adjudicate after user-specified nr of repeats */
6983                              int result = GameIsDrawn;
6984                              char *details = "XBoard adjudication: repetition draw";
6985                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6986                                 // [HGM] xiangqi: check for forbidden perpetuals
6987                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6988                                 for(m=forwardMostMove; m>k; m-=2) {
6989                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6990                                         ourPerpetual = 0; // the current mover did not always check
6991                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6992                                         hisPerpetual = 0; // the opponent did not always check
6993                                 }
6994                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6995                                                                         ourPerpetual, hisPerpetual);
6996                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6997                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6998                                     details = "Xboard adjudication: perpetual checking";
6999                                 } else
7000                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7001                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7002                                 } else
7003                                 // Now check for perpetual chases
7004                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7005                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7006                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7007                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7008                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7009                                         details = "Xboard adjudication: perpetual chasing";
7010                                     } else
7011                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7012                                         break; // Abort repetition-checking loop.
7013                                 }
7014                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7015                              }
7016                              if(engineOpponent) {
7017                                SendToProgram("force\n", engineOpponent); // suppress reply
7018                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7019                              }
7020                              GameEnds( result, details, GE_XBOARD );
7021                              return 1;
7022                         }
7023                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7024                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7025                     }
7026                 }
7027
7028                 /* Now we test for 50-move draws. Determine ply count */
7029                 count = forwardMostMove;
7030                 /* look for last irreversble move */
7031                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7032                     count--;
7033                 /* if we hit starting position, add initial plies */
7034                 if( count == backwardMostMove )
7035                     count -= initialRulePlies;
7036                 count = forwardMostMove - count;
7037                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7038                         // adjust reversible move counter for checks in Xiangqi
7039                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7040                         if(i < backwardMostMove) i = backwardMostMove;
7041                         while(i <= forwardMostMove) {
7042                                 lastCheck = inCheck; // check evasion does not count
7043                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7044                                 if(inCheck || lastCheck) count--; // check does not count
7045                                 i++;
7046                         }
7047                 }
7048                 if( count >= 100)
7049                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7050                          /* this is used to judge if draw claims are legal */
7051                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7052                          if(engineOpponent) {
7053                            SendToProgram("force\n", engineOpponent); // suppress reply
7054                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7055                          }
7056                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7057                          return 1;
7058                 }
7059
7060                 /* if draw offer is pending, treat it as a draw claim
7061                  * when draw condition present, to allow engines a way to
7062                  * claim draws before making their move to avoid a race
7063                  * condition occurring after their move
7064                  */
7065                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7066                          char *p = NULL;
7067                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7068                              p = "Draw claim: 50-move rule";
7069                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7070                              p = "Draw claim: 3-fold repetition";
7071                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7072                              p = "Draw claim: insufficient mating material";
7073                          if( p != NULL && canAdjudicate) {
7074                              if(engineOpponent) {
7075                                SendToProgram("force\n", engineOpponent); // suppress reply
7076                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7077                              }
7078                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7079                              return 1;
7080                          }
7081                 }
7082
7083                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7084                     if(engineOpponent) {
7085                       SendToProgram("force\n", engineOpponent); // suppress reply
7086                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7087                     }
7088                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7089                     return 1;
7090                 }
7091         return 0;
7092 }
7093
7094 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7095 {   // [HGM] book: this routine intercepts moves to simulate book replies
7096     char *bookHit = NULL;
7097
7098     //first determine if the incoming move brings opponent into his book
7099     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7100         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7101     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7102     if(bookHit != NULL && !cps->bookSuspend) {
7103         // make sure opponent is not going to reply after receiving move to book position
7104         SendToProgram("force\n", cps);
7105         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7106     }
7107     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7108     // now arrange restart after book miss
7109     if(bookHit) {
7110         // after a book hit we never send 'go', and the code after the call to this routine
7111         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7112         char buf[MSG_SIZ];
7113         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7114         SendToProgram(buf, cps);
7115         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7116     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7117         SendToProgram("go\n", cps);
7118         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7119     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7120         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7121             SendToProgram("go\n", cps);
7122         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7123     }
7124     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7125 }
7126
7127 char *savedMessage;
7128 ChessProgramState *savedState;
7129 void DeferredBookMove(void)
7130 {
7131         if(savedState->lastPing != savedState->lastPong)
7132                     ScheduleDelayedEvent(DeferredBookMove, 10);
7133         else
7134         HandleMachineMove(savedMessage, savedState);
7135 }
7136
7137 void
7138 HandleMachineMove(message, cps)
7139      char *message;
7140      ChessProgramState *cps;
7141 {
7142     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7143     char realname[MSG_SIZ];
7144     int fromX, fromY, toX, toY;
7145     ChessMove moveType;
7146     char promoChar;
7147     char *p;
7148     int machineWhite;
7149     char *bookHit;
7150
7151     cps->userError = 0;
7152
7153 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7154     /*
7155      * Kludge to ignore BEL characters
7156      */
7157     while (*message == '\007') message++;
7158
7159     /*
7160      * [HGM] engine debug message: ignore lines starting with '#' character
7161      */
7162     if(cps->debug && *message == '#') return;
7163
7164     /*
7165      * Look for book output
7166      */
7167     if (cps == &first && bookRequested) {
7168         if (message[0] == '\t' || message[0] == ' ') {
7169             /* Part of the book output is here; append it */
7170             strcat(bookOutput, message);
7171             strcat(bookOutput, "  \n");
7172             return;
7173         } else if (bookOutput[0] != NULLCHAR) {
7174             /* All of book output has arrived; display it */
7175             char *p = bookOutput;
7176             while (*p != NULLCHAR) {
7177                 if (*p == '\t') *p = ' ';
7178                 p++;
7179             }
7180             DisplayInformation(bookOutput);
7181             bookRequested = FALSE;
7182             /* Fall through to parse the current output */
7183         }
7184     }
7185
7186     /*
7187      * Look for machine move.
7188      */
7189     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7190         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7191     {
7192         /* This method is only useful on engines that support ping */
7193         if (cps->lastPing != cps->lastPong) {
7194           if (gameMode == BeginningOfGame) {
7195             /* Extra move from before last new; ignore */
7196             if (appData.debugMode) {
7197                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7198             }
7199           } else {
7200             if (appData.debugMode) {
7201                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7202                         cps->which, gameMode);
7203             }
7204
7205             SendToProgram("undo\n", cps);
7206           }
7207           return;
7208         }
7209
7210         switch (gameMode) {
7211           case BeginningOfGame:
7212             /* Extra move from before last reset; ignore */
7213             if (appData.debugMode) {
7214                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7215             }
7216             return;
7217
7218           case EndOfGame:
7219           case IcsIdle:
7220           default:
7221             /* Extra move after we tried to stop.  The mode test is
7222                not a reliable way of detecting this problem, but it's
7223                the best we can do on engines that don't support ping.
7224             */
7225             if (appData.debugMode) {
7226                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7227                         cps->which, gameMode);
7228             }
7229             SendToProgram("undo\n", cps);
7230             return;
7231
7232           case MachinePlaysWhite:
7233           case IcsPlayingWhite:
7234             machineWhite = TRUE;
7235             break;
7236
7237           case MachinePlaysBlack:
7238           case IcsPlayingBlack:
7239             machineWhite = FALSE;
7240             break;
7241
7242           case TwoMachinesPlay:
7243             machineWhite = (cps->twoMachinesColor[0] == 'w');
7244             break;
7245         }
7246         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7247             if (appData.debugMode) {
7248                 fprintf(debugFP,
7249                         "Ignoring move out of turn by %s, gameMode %d"
7250                         ", forwardMost %d\n",
7251                         cps->which, gameMode, forwardMostMove);
7252             }
7253             return;
7254         }
7255
7256     if (appData.debugMode) { int f = forwardMostMove;
7257         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7258                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7259                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7260     }
7261         if(cps->alphaRank) AlphaRank(machineMove, 4);
7262         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7263                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7264             /* Machine move could not be parsed; ignore it. */
7265           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7266                     machineMove, cps->which);
7267             DisplayError(buf1, 0);
7268             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7269                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7270             if (gameMode == TwoMachinesPlay) {
7271               GameEnds(machineWhite ? BlackWins : WhiteWins,
7272                        buf1, GE_XBOARD);
7273             }
7274             return;
7275         }
7276
7277         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7278         /* So we have to redo legality test with true e.p. status here,  */
7279         /* to make sure an illegal e.p. capture does not slip through,   */
7280         /* to cause a forfeit on a justified illegal-move complaint      */
7281         /* of the opponent.                                              */
7282         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7283            ChessMove moveType;
7284            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7285                              fromY, fromX, toY, toX, promoChar);
7286             if (appData.debugMode) {
7287                 int i;
7288                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7289                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7290                 fprintf(debugFP, "castling rights\n");
7291             }
7292             if(moveType == IllegalMove) {
7293               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7294                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7295                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7296                            buf1, GE_XBOARD);
7297                 return;
7298            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7299            /* [HGM] Kludge to handle engines that send FRC-style castling
7300               when they shouldn't (like TSCP-Gothic) */
7301            switch(moveType) {
7302              case WhiteASideCastleFR:
7303              case BlackASideCastleFR:
7304                toX+=2;
7305                currentMoveString[2]++;
7306                break;
7307              case WhiteHSideCastleFR:
7308              case BlackHSideCastleFR:
7309                toX--;
7310                currentMoveString[2]--;
7311                break;
7312              default: ; // nothing to do, but suppresses warning of pedantic compilers
7313            }
7314         }
7315         hintRequested = FALSE;
7316         lastHint[0] = NULLCHAR;
7317         bookRequested = FALSE;
7318         /* Program may be pondering now */
7319         cps->maybeThinking = TRUE;
7320         if (cps->sendTime == 2) cps->sendTime = 1;
7321         if (cps->offeredDraw) cps->offeredDraw--;
7322
7323         /* [AS] Save move info*/
7324         pvInfoList[ forwardMostMove ].score = programStats.score;
7325         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7326         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7327
7328         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7329
7330         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7331         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7332             int count = 0;
7333
7334             while( count < adjudicateLossPlies ) {
7335                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7336
7337                 if( count & 1 ) {
7338                     score = -score; /* Flip score for winning side */
7339                 }
7340
7341                 if( score > adjudicateLossThreshold ) {
7342                     break;
7343                 }
7344
7345                 count++;
7346             }
7347
7348             if( count >= adjudicateLossPlies ) {
7349                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7350
7351                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7352                     "Xboard adjudication",
7353                     GE_XBOARD );
7354
7355                 return;
7356             }
7357         }
7358
7359         if(Adjudicate(cps)) {
7360             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7361             return; // [HGM] adjudicate: for all automatic game ends
7362         }
7363
7364 #if ZIPPY
7365         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7366             first.initDone) {
7367           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7368                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7369                 SendToICS("draw ");
7370                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7371           }
7372           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7373           ics_user_moved = 1;
7374           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7375                 char buf[3*MSG_SIZ];
7376
7377                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7378                         programStats.score / 100.,
7379                         programStats.depth,
7380                         programStats.time / 100.,
7381                         (unsigned int)programStats.nodes,
7382                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7383                         programStats.movelist);
7384                 SendToICS(buf);
7385 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7386           }
7387         }
7388 #endif
7389
7390         /* [AS] Clear stats for next move */
7391         ClearProgramStats();
7392         thinkOutput[0] = NULLCHAR;
7393         hiddenThinkOutputState = 0;
7394
7395         bookHit = NULL;
7396         if (gameMode == TwoMachinesPlay) {
7397             /* [HGM] relaying draw offers moved to after reception of move */
7398             /* and interpreting offer as claim if it brings draw condition */
7399             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7400                 SendToProgram("draw\n", cps->other);
7401             }
7402             if (cps->other->sendTime) {
7403                 SendTimeRemaining(cps->other,
7404                                   cps->other->twoMachinesColor[0] == 'w');
7405             }
7406             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7407             if (firstMove && !bookHit) {
7408                 firstMove = FALSE;
7409                 if (cps->other->useColors) {
7410                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7411                 }
7412                 SendToProgram("go\n", cps->other);
7413             }
7414             cps->other->maybeThinking = TRUE;
7415         }
7416
7417         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7418
7419         if (!pausing && appData.ringBellAfterMoves) {
7420             RingBell();
7421         }
7422
7423         /*
7424          * Reenable menu items that were disabled while
7425          * machine was thinking
7426          */
7427         if (gameMode != TwoMachinesPlay)
7428             SetUserThinkingEnables();
7429
7430         // [HGM] book: after book hit opponent has received move and is now in force mode
7431         // force the book reply into it, and then fake that it outputted this move by jumping
7432         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7433         if(bookHit) {
7434                 static char bookMove[MSG_SIZ]; // a bit generous?
7435
7436                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7437                 strcat(bookMove, bookHit);
7438                 message = bookMove;
7439                 cps = cps->other;
7440                 programStats.nodes = programStats.depth = programStats.time =
7441                 programStats.score = programStats.got_only_move = 0;
7442                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7443
7444                 if(cps->lastPing != cps->lastPong) {
7445                     savedMessage = message; // args for deferred call
7446                     savedState = cps;
7447                     ScheduleDelayedEvent(DeferredBookMove, 10);
7448                     return;
7449                 }
7450                 goto FakeBookMove;
7451         }
7452
7453         return;
7454     }
7455
7456     /* Set special modes for chess engines.  Later something general
7457      *  could be added here; for now there is just one kludge feature,
7458      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7459      *  when "xboard" is given as an interactive command.
7460      */
7461     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7462         cps->useSigint = FALSE;
7463         cps->useSigterm = FALSE;
7464     }
7465     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7466       ParseFeatures(message+8, cps);
7467       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7468     }
7469
7470     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7471       int dummy, s=6; char buf[MSG_SIZ];
7472       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7473       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7474       ParseFEN(boards[0], &dummy, message+s);
7475       DrawPosition(TRUE, boards[0]);
7476       startedFromSetupPosition = TRUE;
7477       return;
7478     }
7479     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7480      * want this, I was asked to put it in, and obliged.
7481      */
7482     if (!strncmp(message, "setboard ", 9)) {
7483         Board initial_position;
7484
7485         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7486
7487         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7488             DisplayError(_("Bad FEN received from engine"), 0);
7489             return ;
7490         } else {
7491            Reset(TRUE, FALSE);
7492            CopyBoard(boards[0], initial_position);
7493            initialRulePlies = FENrulePlies;
7494            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7495            else gameMode = MachinePlaysBlack;
7496            DrawPosition(FALSE, boards[currentMove]);
7497         }
7498         return;
7499     }
7500
7501     /*
7502      * Look for communication commands
7503      */
7504     if (!strncmp(message, "telluser ", 9)) {
7505         if(message[9] == '\\' && message[10] == '\\')
7506             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7507         DisplayNote(message + 9);
7508         return;
7509     }
7510     if (!strncmp(message, "tellusererror ", 14)) {
7511         cps->userError = 1;
7512         if(message[14] == '\\' && message[15] == '\\')
7513             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7514         DisplayError(message + 14, 0);
7515         return;
7516     }
7517     if (!strncmp(message, "tellopponent ", 13)) {
7518       if (appData.icsActive) {
7519         if (loggedOn) {
7520           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7521           SendToICS(buf1);
7522         }
7523       } else {
7524         DisplayNote(message + 13);
7525       }
7526       return;
7527     }
7528     if (!strncmp(message, "tellothers ", 11)) {
7529       if (appData.icsActive) {
7530         if (loggedOn) {
7531           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7532           SendToICS(buf1);
7533         }
7534       }
7535       return;
7536     }
7537     if (!strncmp(message, "tellall ", 8)) {
7538       if (appData.icsActive) {
7539         if (loggedOn) {
7540           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7541           SendToICS(buf1);
7542         }
7543       } else {
7544         DisplayNote(message + 8);
7545       }
7546       return;
7547     }
7548     if (strncmp(message, "warning", 7) == 0) {
7549         /* Undocumented feature, use tellusererror in new code */
7550         DisplayError(message, 0);
7551         return;
7552     }
7553     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7554         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7555         strcat(realname, " query");
7556         AskQuestion(realname, buf2, buf1, cps->pr);
7557         return;
7558     }
7559     /* Commands from the engine directly to ICS.  We don't allow these to be
7560      *  sent until we are logged on. Crafty kibitzes have been known to
7561      *  interfere with the login process.
7562      */
7563     if (loggedOn) {
7564         if (!strncmp(message, "tellics ", 8)) {
7565             SendToICS(message + 8);
7566             SendToICS("\n");
7567             return;
7568         }
7569         if (!strncmp(message, "tellicsnoalias ", 15)) {
7570             SendToICS(ics_prefix);
7571             SendToICS(message + 15);
7572             SendToICS("\n");
7573             return;
7574         }
7575         /* The following are for backward compatibility only */
7576         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7577             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7578             SendToICS(ics_prefix);
7579             SendToICS(message);
7580             SendToICS("\n");
7581             return;
7582         }
7583     }
7584     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7585         return;
7586     }
7587     /*
7588      * If the move is illegal, cancel it and redraw the board.
7589      * Also deal with other error cases.  Matching is rather loose
7590      * here to accommodate engines written before the spec.
7591      */
7592     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7593         strncmp(message, "Error", 5) == 0) {
7594         if (StrStr(message, "name") ||
7595             StrStr(message, "rating") || StrStr(message, "?") ||
7596             StrStr(message, "result") || StrStr(message, "board") ||
7597             StrStr(message, "bk") || StrStr(message, "computer") ||
7598             StrStr(message, "variant") || StrStr(message, "hint") ||
7599             StrStr(message, "random") || StrStr(message, "depth") ||
7600             StrStr(message, "accepted")) {
7601             return;
7602         }
7603         if (StrStr(message, "protover")) {
7604           /* Program is responding to input, so it's apparently done
7605              initializing, and this error message indicates it is
7606              protocol version 1.  So we don't need to wait any longer
7607              for it to initialize and send feature commands. */
7608           FeatureDone(cps, 1);
7609           cps->protocolVersion = 1;
7610           return;
7611         }
7612         cps->maybeThinking = FALSE;
7613
7614         if (StrStr(message, "draw")) {
7615             /* Program doesn't have "draw" command */
7616             cps->sendDrawOffers = 0;
7617             return;
7618         }
7619         if (cps->sendTime != 1 &&
7620             (StrStr(message, "time") || StrStr(message, "otim"))) {
7621           /* Program apparently doesn't have "time" or "otim" command */
7622           cps->sendTime = 0;
7623           return;
7624         }
7625         if (StrStr(message, "analyze")) {
7626             cps->analysisSupport = FALSE;
7627             cps->analyzing = FALSE;
7628             Reset(FALSE, TRUE);
7629             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7630             DisplayError(buf2, 0);
7631             return;
7632         }
7633         if (StrStr(message, "(no matching move)st")) {
7634           /* Special kludge for GNU Chess 4 only */
7635           cps->stKludge = TRUE;
7636           SendTimeControl(cps, movesPerSession, timeControl,
7637                           timeIncrement, appData.searchDepth,
7638                           searchTime);
7639           return;
7640         }
7641         if (StrStr(message, "(no matching move)sd")) {
7642           /* Special kludge for GNU Chess 4 only */
7643           cps->sdKludge = TRUE;
7644           SendTimeControl(cps, movesPerSession, timeControl,
7645                           timeIncrement, appData.searchDepth,
7646                           searchTime);
7647           return;
7648         }
7649         if (!StrStr(message, "llegal")) {
7650             return;
7651         }
7652         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7653             gameMode == IcsIdle) return;
7654         if (forwardMostMove <= backwardMostMove) return;
7655         if (pausing) PauseEvent();
7656       if(appData.forceIllegal) {
7657             // [HGM] illegal: machine refused move; force position after move into it
7658           SendToProgram("force\n", cps);
7659           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7660                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7661                 // when black is to move, while there might be nothing on a2 or black
7662                 // might already have the move. So send the board as if white has the move.
7663                 // But first we must change the stm of the engine, as it refused the last move
7664                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7665                 if(WhiteOnMove(forwardMostMove)) {
7666                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7667                     SendBoard(cps, forwardMostMove); // kludgeless board
7668                 } else {
7669                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7670                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7671                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7672                 }
7673           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7674             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7675                  gameMode == TwoMachinesPlay)
7676               SendToProgram("go\n", cps);
7677             return;
7678       } else
7679         if (gameMode == PlayFromGameFile) {
7680             /* Stop reading this game file */
7681             gameMode = EditGame;
7682             ModeHighlight();
7683         }
7684         /* [HGM] illegal-move claim should forfeit game when Xboard */
7685         /* only passes fully legal moves                            */
7686         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7687             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7688                                 "False illegal-move claim", GE_XBOARD );
7689             return; // do not take back move we tested as valid
7690         }
7691         currentMove = forwardMostMove-1;
7692         DisplayMove(currentMove-1); /* before DisplayMoveError */
7693         SwitchClocks(forwardMostMove-1); // [HGM] race
7694         DisplayBothClocks();
7695         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7696                 parseList[currentMove], cps->which);
7697         DisplayMoveError(buf1);
7698         DrawPosition(FALSE, boards[currentMove]);
7699         return;
7700     }
7701     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7702         /* Program has a broken "time" command that
7703            outputs a string not ending in newline.
7704            Don't use it. */
7705         cps->sendTime = 0;
7706     }
7707
7708     /*
7709      * If chess program startup fails, exit with an error message.
7710      * Attempts to recover here are futile.
7711      */
7712     if ((StrStr(message, "unknown host") != NULL)
7713         || (StrStr(message, "No remote directory") != NULL)
7714         || (StrStr(message, "not found") != NULL)
7715         || (StrStr(message, "No such file") != NULL)
7716         || (StrStr(message, "can't alloc") != NULL)
7717         || (StrStr(message, "Permission denied") != NULL)) {
7718
7719         cps->maybeThinking = FALSE;
7720         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7721                 cps->which, cps->program, cps->host, message);
7722         RemoveInputSource(cps->isr);
7723         DisplayFatalError(buf1, 0, 1);
7724         return;
7725     }
7726
7727     /*
7728      * Look for hint output
7729      */
7730     if (sscanf(message, "Hint: %s", buf1) == 1) {
7731         if (cps == &first && hintRequested) {
7732             hintRequested = FALSE;
7733             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7734                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7735                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7736                                     PosFlags(forwardMostMove),
7737                                     fromY, fromX, toY, toX, promoChar, buf1);
7738                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7739                 DisplayInformation(buf2);
7740             } else {
7741                 /* Hint move could not be parsed!? */
7742               snprintf(buf2, sizeof(buf2),
7743                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7744                         buf1, cps->which);
7745                 DisplayError(buf2, 0);
7746             }
7747         } else {
7748           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7749         }
7750         return;
7751     }
7752
7753     /*
7754      * Ignore other messages if game is not in progress
7755      */
7756     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7757         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7758
7759     /*
7760      * look for win, lose, draw, or draw offer
7761      */
7762     if (strncmp(message, "1-0", 3) == 0) {
7763         char *p, *q, *r = "";
7764         p = strchr(message, '{');
7765         if (p) {
7766             q = strchr(p, '}');
7767             if (q) {
7768                 *q = NULLCHAR;
7769                 r = p + 1;
7770             }
7771         }
7772         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7773         return;
7774     } else if (strncmp(message, "0-1", 3) == 0) {
7775         char *p, *q, *r = "";
7776         p = strchr(message, '{');
7777         if (p) {
7778             q = strchr(p, '}');
7779             if (q) {
7780                 *q = NULLCHAR;
7781                 r = p + 1;
7782             }
7783         }
7784         /* Kludge for Arasan 4.1 bug */
7785         if (strcmp(r, "Black resigns") == 0) {
7786             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7787             return;
7788         }
7789         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7790         return;
7791     } else if (strncmp(message, "1/2", 3) == 0) {
7792         char *p, *q, *r = "";
7793         p = strchr(message, '{');
7794         if (p) {
7795             q = strchr(p, '}');
7796             if (q) {
7797                 *q = NULLCHAR;
7798                 r = p + 1;
7799             }
7800         }
7801
7802         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7803         return;
7804
7805     } else if (strncmp(message, "White resign", 12) == 0) {
7806         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7807         return;
7808     } else if (strncmp(message, "Black resign", 12) == 0) {
7809         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7810         return;
7811     } else if (strncmp(message, "White matches", 13) == 0 ||
7812                strncmp(message, "Black matches", 13) == 0   ) {
7813         /* [HGM] ignore GNUShogi noises */
7814         return;
7815     } else if (strncmp(message, "White", 5) == 0 &&
7816                message[5] != '(' &&
7817                StrStr(message, "Black") == NULL) {
7818         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7819         return;
7820     } else if (strncmp(message, "Black", 5) == 0 &&
7821                message[5] != '(') {
7822         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7823         return;
7824     } else if (strcmp(message, "resign") == 0 ||
7825                strcmp(message, "computer resigns") == 0) {
7826         switch (gameMode) {
7827           case MachinePlaysBlack:
7828           case IcsPlayingBlack:
7829             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7830             break;
7831           case MachinePlaysWhite:
7832           case IcsPlayingWhite:
7833             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7834             break;
7835           case TwoMachinesPlay:
7836             if (cps->twoMachinesColor[0] == 'w')
7837               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7838             else
7839               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7840             break;
7841           default:
7842             /* can't happen */
7843             break;
7844         }
7845         return;
7846     } else if (strncmp(message, "opponent mates", 14) == 0) {
7847         switch (gameMode) {
7848           case MachinePlaysBlack:
7849           case IcsPlayingBlack:
7850             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7851             break;
7852           case MachinePlaysWhite:
7853           case IcsPlayingWhite:
7854             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7855             break;
7856           case TwoMachinesPlay:
7857             if (cps->twoMachinesColor[0] == 'w')
7858               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7859             else
7860               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7861             break;
7862           default:
7863             /* can't happen */
7864             break;
7865         }
7866         return;
7867     } else if (strncmp(message, "computer mates", 14) == 0) {
7868         switch (gameMode) {
7869           case MachinePlaysBlack:
7870           case IcsPlayingBlack:
7871             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7872             break;
7873           case MachinePlaysWhite:
7874           case IcsPlayingWhite:
7875             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7876             break;
7877           case TwoMachinesPlay:
7878             if (cps->twoMachinesColor[0] == 'w')
7879               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7880             else
7881               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7882             break;
7883           default:
7884             /* can't happen */
7885             break;
7886         }
7887         return;
7888     } else if (strncmp(message, "checkmate", 9) == 0) {
7889         if (WhiteOnMove(forwardMostMove)) {
7890             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7891         } else {
7892             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7893         }
7894         return;
7895     } else if (strstr(message, "Draw") != NULL ||
7896                strstr(message, "game is a draw") != NULL) {
7897         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7898         return;
7899     } else if (strstr(message, "offer") != NULL &&
7900                strstr(message, "draw") != NULL) {
7901 #if ZIPPY
7902         if (appData.zippyPlay && first.initDone) {
7903             /* Relay offer to ICS */
7904             SendToICS(ics_prefix);
7905             SendToICS("draw\n");
7906         }
7907 #endif
7908         cps->offeredDraw = 2; /* valid until this engine moves twice */
7909         if (gameMode == TwoMachinesPlay) {
7910             if (cps->other->offeredDraw) {
7911                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7912             /* [HGM] in two-machine mode we delay relaying draw offer      */
7913             /* until after we also have move, to see if it is really claim */
7914             }
7915         } else if (gameMode == MachinePlaysWhite ||
7916                    gameMode == MachinePlaysBlack) {
7917           if (userOfferedDraw) {
7918             DisplayInformation(_("Machine accepts your draw offer"));
7919             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7920           } else {
7921             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7922           }
7923         }
7924     }
7925
7926
7927     /*
7928      * Look for thinking output
7929      */
7930     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7931           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7932                                 ) {
7933         int plylev, mvleft, mvtot, curscore, time;
7934         char mvname[MOVE_LEN];
7935         u64 nodes; // [DM]
7936         char plyext;
7937         int ignore = FALSE;
7938         int prefixHint = FALSE;
7939         mvname[0] = NULLCHAR;
7940
7941         switch (gameMode) {
7942           case MachinePlaysBlack:
7943           case IcsPlayingBlack:
7944             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7945             break;
7946           case MachinePlaysWhite:
7947           case IcsPlayingWhite:
7948             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7949             break;
7950           case AnalyzeMode:
7951           case AnalyzeFile:
7952             break;
7953           case IcsObserving: /* [DM] icsEngineAnalyze */
7954             if (!appData.icsEngineAnalyze) ignore = TRUE;
7955             break;
7956           case TwoMachinesPlay:
7957             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7958                 ignore = TRUE;
7959             }
7960             break;
7961           default:
7962             ignore = TRUE;
7963             break;
7964         }
7965
7966         if (!ignore) {
7967             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7968             buf1[0] = NULLCHAR;
7969             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7970                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7971
7972                 if (plyext != ' ' && plyext != '\t') {
7973                     time *= 100;
7974                 }
7975
7976                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7977                 if( cps->scoreIsAbsolute &&
7978                     ( gameMode == MachinePlaysBlack ||
7979                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7980                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7981                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7982                      !WhiteOnMove(currentMove)
7983                     ) )
7984                 {
7985                     curscore = -curscore;
7986                 }
7987
7988
7989                 tempStats.depth = plylev;
7990                 tempStats.nodes = nodes;
7991                 tempStats.time = time;
7992                 tempStats.score = curscore;
7993                 tempStats.got_only_move = 0;
7994
7995                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7996                         int ticklen;
7997
7998                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7999                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8000                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8001                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8002                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8003                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8004                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8005                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8006                 }
8007
8008                 /* Buffer overflow protection */
8009                 if (buf1[0] != NULLCHAR) {
8010                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8011                         && appData.debugMode) {
8012                         fprintf(debugFP,
8013                                 "PV is too long; using the first %u bytes.\n",
8014                                 (unsigned) sizeof(tempStats.movelist) - 1);
8015                     }
8016
8017                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8018                 } else {
8019                     sprintf(tempStats.movelist, " no PV\n");
8020                 }
8021
8022                 if (tempStats.seen_stat) {
8023                     tempStats.ok_to_send = 1;
8024                 }
8025
8026                 if (strchr(tempStats.movelist, '(') != NULL) {
8027                     tempStats.line_is_book = 1;
8028                     tempStats.nr_moves = 0;
8029                     tempStats.moves_left = 0;
8030                 } else {
8031                     tempStats.line_is_book = 0;
8032                 }
8033
8034                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8035                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8036
8037                 SendProgramStatsToFrontend( cps, &tempStats );
8038
8039                 /*
8040                     [AS] Protect the thinkOutput buffer from overflow... this
8041                     is only useful if buf1 hasn't overflowed first!
8042                 */
8043                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8044                          plylev,
8045                          (gameMode == TwoMachinesPlay ?
8046                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8047                          ((double) curscore) / 100.0,
8048                          prefixHint ? lastHint : "",
8049                          prefixHint ? " " : "" );
8050
8051                 if( buf1[0] != NULLCHAR ) {
8052                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8053
8054                     if( strlen(buf1) > max_len ) {
8055                         if( appData.debugMode) {
8056                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8057                         }
8058                         buf1[max_len+1] = '\0';
8059                     }
8060
8061                     strcat( thinkOutput, buf1 );
8062                 }
8063
8064                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8065                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8066                     DisplayMove(currentMove - 1);
8067                 }
8068                 return;
8069
8070             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8071                 /* crafty (9.25+) says "(only move) <move>"
8072                  * if there is only 1 legal move
8073                  */
8074                 sscanf(p, "(only move) %s", buf1);
8075                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8076                 sprintf(programStats.movelist, "%s (only move)", buf1);
8077                 programStats.depth = 1;
8078                 programStats.nr_moves = 1;
8079                 programStats.moves_left = 1;
8080                 programStats.nodes = 1;
8081                 programStats.time = 1;
8082                 programStats.got_only_move = 1;
8083
8084                 /* Not really, but we also use this member to
8085                    mean "line isn't going to change" (Crafty
8086                    isn't searching, so stats won't change) */
8087                 programStats.line_is_book = 1;
8088
8089                 SendProgramStatsToFrontend( cps, &programStats );
8090
8091                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8092                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8093                     DisplayMove(currentMove - 1);
8094                 }
8095                 return;
8096             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8097                               &time, &nodes, &plylev, &mvleft,
8098                               &mvtot, mvname) >= 5) {
8099                 /* The stat01: line is from Crafty (9.29+) in response
8100                    to the "." command */
8101                 programStats.seen_stat = 1;
8102                 cps->maybeThinking = TRUE;
8103
8104                 if (programStats.got_only_move || !appData.periodicUpdates)
8105                   return;
8106
8107                 programStats.depth = plylev;
8108                 programStats.time = time;
8109                 programStats.nodes = nodes;
8110                 programStats.moves_left = mvleft;
8111                 programStats.nr_moves = mvtot;
8112                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8113                 programStats.ok_to_send = 1;
8114                 programStats.movelist[0] = '\0';
8115
8116                 SendProgramStatsToFrontend( cps, &programStats );
8117
8118                 return;
8119
8120             } else if (strncmp(message,"++",2) == 0) {
8121                 /* Crafty 9.29+ outputs this */
8122                 programStats.got_fail = 2;
8123                 return;
8124
8125             } else if (strncmp(message,"--",2) == 0) {
8126                 /* Crafty 9.29+ outputs this */
8127                 programStats.got_fail = 1;
8128                 return;
8129
8130             } else if (thinkOutput[0] != NULLCHAR &&
8131                        strncmp(message, "    ", 4) == 0) {
8132                 unsigned message_len;
8133
8134                 p = message;
8135                 while (*p && *p == ' ') p++;
8136
8137                 message_len = strlen( p );
8138
8139                 /* [AS] Avoid buffer overflow */
8140                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8141                     strcat(thinkOutput, " ");
8142                     strcat(thinkOutput, p);
8143                 }
8144
8145                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8146                     strcat(programStats.movelist, " ");
8147                     strcat(programStats.movelist, p);
8148                 }
8149
8150                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8151                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8152                     DisplayMove(currentMove - 1);
8153                 }
8154                 return;
8155             }
8156         }
8157         else {
8158             buf1[0] = NULLCHAR;
8159
8160             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8161                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8162             {
8163                 ChessProgramStats cpstats;
8164
8165                 if (plyext != ' ' && plyext != '\t') {
8166                     time *= 100;
8167                 }
8168
8169                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8170                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8171                     curscore = -curscore;
8172                 }
8173
8174                 cpstats.depth = plylev;
8175                 cpstats.nodes = nodes;
8176                 cpstats.time = time;
8177                 cpstats.score = curscore;
8178                 cpstats.got_only_move = 0;
8179                 cpstats.movelist[0] = '\0';
8180
8181                 if (buf1[0] != NULLCHAR) {
8182                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8183                 }
8184
8185                 cpstats.ok_to_send = 0;
8186                 cpstats.line_is_book = 0;
8187                 cpstats.nr_moves = 0;
8188                 cpstats.moves_left = 0;
8189
8190                 SendProgramStatsToFrontend( cps, &cpstats );
8191             }
8192         }
8193     }
8194 }
8195
8196
8197 /* Parse a game score from the character string "game", and
8198    record it as the history of the current game.  The game
8199    score is NOT assumed to start from the standard position.
8200    The display is not updated in any way.
8201    */
8202 void
8203 ParseGameHistory(game)
8204      char *game;
8205 {
8206     ChessMove moveType;
8207     int fromX, fromY, toX, toY, boardIndex;
8208     char promoChar;
8209     char *p, *q;
8210     char buf[MSG_SIZ];
8211
8212     if (appData.debugMode)
8213       fprintf(debugFP, "Parsing game history: %s\n", game);
8214
8215     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8216     gameInfo.site = StrSave(appData.icsHost);
8217     gameInfo.date = PGNDate();
8218     gameInfo.round = StrSave("-");
8219
8220     /* Parse out names of players */
8221     while (*game == ' ') game++;
8222     p = buf;
8223     while (*game != ' ') *p++ = *game++;
8224     *p = NULLCHAR;
8225     gameInfo.white = StrSave(buf);
8226     while (*game == ' ') game++;
8227     p = buf;
8228     while (*game != ' ' && *game != '\n') *p++ = *game++;
8229     *p = NULLCHAR;
8230     gameInfo.black = StrSave(buf);
8231
8232     /* Parse moves */
8233     boardIndex = blackPlaysFirst ? 1 : 0;
8234     yynewstr(game);
8235     for (;;) {
8236         yyboardindex = boardIndex;
8237         moveType = (ChessMove) Myylex();
8238         switch (moveType) {
8239           case IllegalMove:             /* maybe suicide chess, etc. */
8240   if (appData.debugMode) {
8241     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8242     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8243     setbuf(debugFP, NULL);
8244   }
8245           case WhitePromotion:
8246           case BlackPromotion:
8247           case WhiteNonPromotion:
8248           case BlackNonPromotion:
8249           case NormalMove:
8250           case WhiteCapturesEnPassant:
8251           case BlackCapturesEnPassant:
8252           case WhiteKingSideCastle:
8253           case WhiteQueenSideCastle:
8254           case BlackKingSideCastle:
8255           case BlackQueenSideCastle:
8256           case WhiteKingSideCastleWild:
8257           case WhiteQueenSideCastleWild:
8258           case BlackKingSideCastleWild:
8259           case BlackQueenSideCastleWild:
8260           /* PUSH Fabien */
8261           case WhiteHSideCastleFR:
8262           case WhiteASideCastleFR:
8263           case BlackHSideCastleFR:
8264           case BlackASideCastleFR:
8265           /* POP Fabien */
8266             fromX = currentMoveString[0] - AAA;
8267             fromY = currentMoveString[1] - ONE;
8268             toX = currentMoveString[2] - AAA;
8269             toY = currentMoveString[3] - ONE;
8270             promoChar = currentMoveString[4];
8271             break;
8272           case WhiteDrop:
8273           case BlackDrop:
8274             fromX = moveType == WhiteDrop ?
8275               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8276             (int) CharToPiece(ToLower(currentMoveString[0]));
8277             fromY = DROP_RANK;
8278             toX = currentMoveString[2] - AAA;
8279             toY = currentMoveString[3] - ONE;
8280             promoChar = NULLCHAR;
8281             break;
8282           case AmbiguousMove:
8283             /* bug? */
8284             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8285   if (appData.debugMode) {
8286     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8287     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8288     setbuf(debugFP, NULL);
8289   }
8290             DisplayError(buf, 0);
8291             return;
8292           case ImpossibleMove:
8293             /* bug? */
8294             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8295   if (appData.debugMode) {
8296     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8297     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8298     setbuf(debugFP, NULL);
8299   }
8300             DisplayError(buf, 0);
8301             return;
8302           case EndOfFile:
8303             if (boardIndex < backwardMostMove) {
8304                 /* Oops, gap.  How did that happen? */
8305                 DisplayError(_("Gap in move list"), 0);
8306                 return;
8307             }
8308             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8309             if (boardIndex > forwardMostMove) {
8310                 forwardMostMove = boardIndex;
8311             }
8312             return;
8313           case ElapsedTime:
8314             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8315                 strcat(parseList[boardIndex-1], " ");
8316                 strcat(parseList[boardIndex-1], yy_text);
8317             }
8318             continue;
8319           case Comment:
8320           case PGNTag:
8321           case NAG:
8322           default:
8323             /* ignore */
8324             continue;
8325           case WhiteWins:
8326           case BlackWins:
8327           case GameIsDrawn:
8328           case GameUnfinished:
8329             if (gameMode == IcsExamining) {
8330                 if (boardIndex < backwardMostMove) {
8331                     /* Oops, gap.  How did that happen? */
8332                     return;
8333                 }
8334                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8335                 return;
8336             }
8337             gameInfo.result = moveType;
8338             p = strchr(yy_text, '{');
8339             if (p == NULL) p = strchr(yy_text, '(');
8340             if (p == NULL) {
8341                 p = yy_text;
8342                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8343             } else {
8344                 q = strchr(p, *p == '{' ? '}' : ')');
8345                 if (q != NULL) *q = NULLCHAR;
8346                 p++;
8347             }
8348             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8349             gameInfo.resultDetails = StrSave(p);
8350             continue;
8351         }
8352         if (boardIndex >= forwardMostMove &&
8353             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8354             backwardMostMove = blackPlaysFirst ? 1 : 0;
8355             return;
8356         }
8357         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8358                                  fromY, fromX, toY, toX, promoChar,
8359                                  parseList[boardIndex]);
8360         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8361         /* currentMoveString is set as a side-effect of yylex */
8362         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8363         strcat(moveList[boardIndex], "\n");
8364         boardIndex++;
8365         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8366         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8367           case MT_NONE:
8368           case MT_STALEMATE:
8369           default:
8370             break;
8371           case MT_CHECK:
8372             if(gameInfo.variant != VariantShogi)
8373                 strcat(parseList[boardIndex - 1], "+");
8374             break;
8375           case MT_CHECKMATE:
8376           case MT_STAINMATE:
8377             strcat(parseList[boardIndex - 1], "#");
8378             break;
8379         }
8380     }
8381 }
8382
8383
8384 /* Apply a move to the given board  */
8385 void
8386 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8387      int fromX, fromY, toX, toY;
8388      int promoChar;
8389      Board board;
8390 {
8391   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8392   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8393
8394     /* [HGM] compute & store e.p. status and castling rights for new position */
8395     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8396
8397       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8398       oldEP = (signed char)board[EP_STATUS];
8399       board[EP_STATUS] = EP_NONE;
8400
8401       if( board[toY][toX] != EmptySquare )
8402            board[EP_STATUS] = EP_CAPTURE;
8403
8404   if (fromY == DROP_RANK) {
8405         /* must be first */
8406         piece = board[toY][toX] = (ChessSquare) fromX;
8407   } else {
8408       int i;
8409
8410       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8411            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8412                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8413       } else
8414       if( board[fromY][fromX] == WhitePawn ) {
8415            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8416                board[EP_STATUS] = EP_PAWN_MOVE;
8417            if( toY-fromY==2) {
8418                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8419                         gameInfo.variant != VariantBerolina || toX < fromX)
8420                       board[EP_STATUS] = toX | berolina;
8421                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8422                         gameInfo.variant != VariantBerolina || toX > fromX)
8423                       board[EP_STATUS] = toX;
8424            }
8425       } else
8426       if( board[fromY][fromX] == BlackPawn ) {
8427            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8428                board[EP_STATUS] = EP_PAWN_MOVE;
8429            if( toY-fromY== -2) {
8430                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8431                         gameInfo.variant != VariantBerolina || toX < fromX)
8432                       board[EP_STATUS] = toX | berolina;
8433                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8434                         gameInfo.variant != VariantBerolina || toX > fromX)
8435                       board[EP_STATUS] = toX;
8436            }
8437        }
8438
8439        for(i=0; i<nrCastlingRights; i++) {
8440            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8441               board[CASTLING][i] == toX   && castlingRank[i] == toY
8442              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8443        }
8444
8445      if (fromX == toX && fromY == toY) return;
8446
8447      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8448      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8449      if(gameInfo.variant == VariantKnightmate)
8450          king += (int) WhiteUnicorn - (int) WhiteKing;
8451
8452     /* Code added by Tord: */
8453     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8454     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8455         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8456       board[fromY][fromX] = EmptySquare;
8457       board[toY][toX] = EmptySquare;
8458       if((toX > fromX) != (piece == WhiteRook)) {
8459         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8460       } else {
8461         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8462       }
8463     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8464                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8465       board[fromY][fromX] = EmptySquare;
8466       board[toY][toX] = EmptySquare;
8467       if((toX > fromX) != (piece == BlackRook)) {
8468         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8469       } else {
8470         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8471       }
8472     /* End of code added by Tord */
8473
8474     } else if (board[fromY][fromX] == king
8475         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8476         && toY == fromY && toX > fromX+1) {
8477         board[fromY][fromX] = EmptySquare;
8478         board[toY][toX] = king;
8479         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8480         board[fromY][BOARD_RGHT-1] = EmptySquare;
8481     } else if (board[fromY][fromX] == king
8482         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8483                && toY == fromY && toX < fromX-1) {
8484         board[fromY][fromX] = EmptySquare;
8485         board[toY][toX] = king;
8486         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8487         board[fromY][BOARD_LEFT] = EmptySquare;
8488     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8489                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8490                && toY >= BOARD_HEIGHT-promoRank
8491                ) {
8492         /* white pawn promotion */
8493         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8494         if (board[toY][toX] == EmptySquare) {
8495             board[toY][toX] = WhiteQueen;
8496         }
8497         if(gameInfo.variant==VariantBughouse ||
8498            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8499             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8500         board[fromY][fromX] = EmptySquare;
8501     } else if ((fromY == BOARD_HEIGHT-4)
8502                && (toX != fromX)
8503                && gameInfo.variant != VariantXiangqi
8504                && gameInfo.variant != VariantBerolina
8505                && (board[fromY][fromX] == WhitePawn)
8506                && (board[toY][toX] == EmptySquare)) {
8507         board[fromY][fromX] = EmptySquare;
8508         board[toY][toX] = WhitePawn;
8509         captured = board[toY - 1][toX];
8510         board[toY - 1][toX] = EmptySquare;
8511     } else if ((fromY == BOARD_HEIGHT-4)
8512                && (toX == fromX)
8513                && gameInfo.variant == VariantBerolina
8514                && (board[fromY][fromX] == WhitePawn)
8515                && (board[toY][toX] == EmptySquare)) {
8516         board[fromY][fromX] = EmptySquare;
8517         board[toY][toX] = WhitePawn;
8518         if(oldEP & EP_BEROLIN_A) {
8519                 captured = board[fromY][fromX-1];
8520                 board[fromY][fromX-1] = EmptySquare;
8521         }else{  captured = board[fromY][fromX+1];
8522                 board[fromY][fromX+1] = EmptySquare;
8523         }
8524     } else if (board[fromY][fromX] == king
8525         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8526                && toY == fromY && toX > fromX+1) {
8527         board[fromY][fromX] = EmptySquare;
8528         board[toY][toX] = king;
8529         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8530         board[fromY][BOARD_RGHT-1] = EmptySquare;
8531     } else if (board[fromY][fromX] == king
8532         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8533                && toY == fromY && toX < fromX-1) {
8534         board[fromY][fromX] = EmptySquare;
8535         board[toY][toX] = king;
8536         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8537         board[fromY][BOARD_LEFT] = EmptySquare;
8538     } else if (fromY == 7 && fromX == 3
8539                && board[fromY][fromX] == BlackKing
8540                && toY == 7 && toX == 5) {
8541         board[fromY][fromX] = EmptySquare;
8542         board[toY][toX] = BlackKing;
8543         board[fromY][7] = EmptySquare;
8544         board[toY][4] = BlackRook;
8545     } else if (fromY == 7 && fromX == 3
8546                && board[fromY][fromX] == BlackKing
8547                && toY == 7 && toX == 1) {
8548         board[fromY][fromX] = EmptySquare;
8549         board[toY][toX] = BlackKing;
8550         board[fromY][0] = EmptySquare;
8551         board[toY][2] = BlackRook;
8552     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8553                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8554                && toY < promoRank
8555                ) {
8556         /* black pawn promotion */
8557         board[toY][toX] = CharToPiece(ToLower(promoChar));
8558         if (board[toY][toX] == EmptySquare) {
8559             board[toY][toX] = BlackQueen;
8560         }
8561         if(gameInfo.variant==VariantBughouse ||
8562            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8563             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8564         board[fromY][fromX] = EmptySquare;
8565     } else if ((fromY == 3)
8566                && (toX != fromX)
8567                && gameInfo.variant != VariantXiangqi
8568                && gameInfo.variant != VariantBerolina
8569                && (board[fromY][fromX] == BlackPawn)
8570                && (board[toY][toX] == EmptySquare)) {
8571         board[fromY][fromX] = EmptySquare;
8572         board[toY][toX] = BlackPawn;
8573         captured = board[toY + 1][toX];
8574         board[toY + 1][toX] = EmptySquare;
8575     } else if ((fromY == 3)
8576                && (toX == fromX)
8577                && gameInfo.variant == VariantBerolina
8578                && (board[fromY][fromX] == BlackPawn)
8579                && (board[toY][toX] == EmptySquare)) {
8580         board[fromY][fromX] = EmptySquare;
8581         board[toY][toX] = BlackPawn;
8582         if(oldEP & EP_BEROLIN_A) {
8583                 captured = board[fromY][fromX-1];
8584                 board[fromY][fromX-1] = EmptySquare;
8585         }else{  captured = board[fromY][fromX+1];
8586                 board[fromY][fromX+1] = EmptySquare;
8587         }
8588     } else {
8589         board[toY][toX] = board[fromY][fromX];
8590         board[fromY][fromX] = EmptySquare;
8591     }
8592   }
8593
8594     if (gameInfo.holdingsWidth != 0) {
8595
8596       /* !!A lot more code needs to be written to support holdings  */
8597       /* [HGM] OK, so I have written it. Holdings are stored in the */
8598       /* penultimate board files, so they are automaticlly stored   */
8599       /* in the game history.                                       */
8600       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8601                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8602         /* Delete from holdings, by decreasing count */
8603         /* and erasing image if necessary            */
8604         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8605         if(p < (int) BlackPawn) { /* white drop */
8606              p -= (int)WhitePawn;
8607                  p = PieceToNumber((ChessSquare)p);
8608              if(p >= gameInfo.holdingsSize) p = 0;
8609              if(--board[p][BOARD_WIDTH-2] <= 0)
8610                   board[p][BOARD_WIDTH-1] = EmptySquare;
8611              if((int)board[p][BOARD_WIDTH-2] < 0)
8612                         board[p][BOARD_WIDTH-2] = 0;
8613         } else {                  /* black drop */
8614              p -= (int)BlackPawn;
8615                  p = PieceToNumber((ChessSquare)p);
8616              if(p >= gameInfo.holdingsSize) p = 0;
8617              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8618                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8619              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8620                         board[BOARD_HEIGHT-1-p][1] = 0;
8621         }
8622       }
8623       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8624           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8625         /* [HGM] holdings: Add to holdings, if holdings exist */
8626         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8627                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8628                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8629         }
8630         p = (int) captured;
8631         if (p >= (int) BlackPawn) {
8632           p -= (int)BlackPawn;
8633           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8634                   /* in Shogi restore piece to its original  first */
8635                   captured = (ChessSquare) (DEMOTED captured);
8636                   p = DEMOTED p;
8637           }
8638           p = PieceToNumber((ChessSquare)p);
8639           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8640           board[p][BOARD_WIDTH-2]++;
8641           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8642         } else {
8643           p -= (int)WhitePawn;
8644           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8645                   captured = (ChessSquare) (DEMOTED captured);
8646                   p = DEMOTED p;
8647           }
8648           p = PieceToNumber((ChessSquare)p);
8649           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8650           board[BOARD_HEIGHT-1-p][1]++;
8651           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8652         }
8653       }
8654     } else if (gameInfo.variant == VariantAtomic) {
8655       if (captured != EmptySquare) {
8656         int y, x;
8657         for (y = toY-1; y <= toY+1; y++) {
8658           for (x = toX-1; x <= toX+1; x++) {
8659             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8660                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8661               board[y][x] = EmptySquare;
8662             }
8663           }
8664         }
8665         board[toY][toX] = EmptySquare;
8666       }
8667     }
8668     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8669         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8670     } else
8671     if(promoChar == '+') {
8672         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8673         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8674     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8675         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8676     }
8677     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8678                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8679         // [HGM] superchess: take promotion piece out of holdings
8680         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8681         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8682             if(!--board[k][BOARD_WIDTH-2])
8683                 board[k][BOARD_WIDTH-1] = EmptySquare;
8684         } else {
8685             if(!--board[BOARD_HEIGHT-1-k][1])
8686                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8687         }
8688     }
8689
8690 }
8691
8692 /* Updates forwardMostMove */
8693 void
8694 MakeMove(fromX, fromY, toX, toY, promoChar)
8695      int fromX, fromY, toX, toY;
8696      int promoChar;
8697 {
8698 //    forwardMostMove++; // [HGM] bare: moved downstream
8699
8700     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8701         int timeLeft; static int lastLoadFlag=0; int king, piece;
8702         piece = boards[forwardMostMove][fromY][fromX];
8703         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8704         if(gameInfo.variant == VariantKnightmate)
8705             king += (int) WhiteUnicorn - (int) WhiteKing;
8706         if(forwardMostMove == 0) {
8707             if(blackPlaysFirst)
8708                 fprintf(serverMoves, "%s;", second.tidy);
8709             fprintf(serverMoves, "%s;", first.tidy);
8710             if(!blackPlaysFirst)
8711                 fprintf(serverMoves, "%s;", second.tidy);
8712         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8713         lastLoadFlag = loadFlag;
8714         // print base move
8715         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8716         // print castling suffix
8717         if( toY == fromY && piece == king ) {
8718             if(toX-fromX > 1)
8719                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8720             if(fromX-toX >1)
8721                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8722         }
8723         // e.p. suffix
8724         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8725              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8726              boards[forwardMostMove][toY][toX] == EmptySquare
8727              && fromX != toX && fromY != toY)
8728                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8729         // promotion suffix
8730         if(promoChar != NULLCHAR)
8731                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8732         if(!loadFlag) {
8733             fprintf(serverMoves, "/%d/%d",
8734                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8735             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8736             else                      timeLeft = blackTimeRemaining/1000;
8737             fprintf(serverMoves, "/%d", timeLeft);
8738         }
8739         fflush(serverMoves);
8740     }
8741
8742     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8743       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8744                         0, 1);
8745       return;
8746     }
8747     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8748     if (commentList[forwardMostMove+1] != NULL) {
8749         free(commentList[forwardMostMove+1]);
8750         commentList[forwardMostMove+1] = NULL;
8751     }
8752     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8753     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8754     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8755     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8756     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8757     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8758     gameInfo.result = GameUnfinished;
8759     if (gameInfo.resultDetails != NULL) {
8760         free(gameInfo.resultDetails);
8761         gameInfo.resultDetails = NULL;
8762     }
8763     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8764                               moveList[forwardMostMove - 1]);
8765     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8766                              PosFlags(forwardMostMove - 1),
8767                              fromY, fromX, toY, toX, promoChar,
8768                              parseList[forwardMostMove - 1]);
8769     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8770       case MT_NONE:
8771       case MT_STALEMATE:
8772       default:
8773         break;
8774       case MT_CHECK:
8775         if(gameInfo.variant != VariantShogi)
8776             strcat(parseList[forwardMostMove - 1], "+");
8777         break;
8778       case MT_CHECKMATE:
8779       case MT_STAINMATE:
8780         strcat(parseList[forwardMostMove - 1], "#");
8781         break;
8782     }
8783     if (appData.debugMode) {
8784         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8785     }
8786
8787 }
8788
8789 /* Updates currentMove if not pausing */
8790 void
8791 ShowMove(fromX, fromY, toX, toY)
8792 {
8793     int instant = (gameMode == PlayFromGameFile) ?
8794         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8795     if(appData.noGUI) return;
8796     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8797         if (!instant) {
8798             if (forwardMostMove == currentMove + 1) {
8799                 AnimateMove(boards[forwardMostMove - 1],
8800                             fromX, fromY, toX, toY);
8801             }
8802             if (appData.highlightLastMove) {
8803                 SetHighlights(fromX, fromY, toX, toY);
8804             }
8805         }
8806         currentMove = forwardMostMove;
8807     }
8808
8809     if (instant) return;
8810
8811     DisplayMove(currentMove - 1);
8812     DrawPosition(FALSE, boards[currentMove]);
8813     DisplayBothClocks();
8814     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8815 }
8816
8817 void SendEgtPath(ChessProgramState *cps)
8818 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8819         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8820
8821         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8822
8823         while(*p) {
8824             char c, *q = name+1, *r, *s;
8825
8826             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8827             while(*p && *p != ',') *q++ = *p++;
8828             *q++ = ':'; *q = 0;
8829             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8830                 strcmp(name, ",nalimov:") == 0 ) {
8831                 // take nalimov path from the menu-changeable option first, if it is defined
8832               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8833                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8834             } else
8835             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8836                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8837                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8838                 s = r = StrStr(s, ":") + 1; // beginning of path info
8839                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8840                 c = *r; *r = 0;             // temporarily null-terminate path info
8841                     *--q = 0;               // strip of trailig ':' from name
8842                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8843                 *r = c;
8844                 SendToProgram(buf,cps);     // send egtbpath command for this format
8845             }
8846             if(*p == ',') p++; // read away comma to position for next format name
8847         }
8848 }
8849
8850 void
8851 InitChessProgram(cps, setup)
8852      ChessProgramState *cps;
8853      int setup; /* [HGM] needed to setup FRC opening position */
8854 {
8855     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8856     if (appData.noChessProgram) return;
8857     hintRequested = FALSE;
8858     bookRequested = FALSE;
8859
8860     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8861     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8862     if(cps->memSize) { /* [HGM] memory */
8863       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8864         SendToProgram(buf, cps);
8865     }
8866     SendEgtPath(cps); /* [HGM] EGT */
8867     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8868       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8869         SendToProgram(buf, cps);
8870     }
8871
8872     SendToProgram(cps->initString, cps);
8873     if (gameInfo.variant != VariantNormal &&
8874         gameInfo.variant != VariantLoadable
8875         /* [HGM] also send variant if board size non-standard */
8876         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8877                                             ) {
8878       char *v = VariantName(gameInfo.variant);
8879       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8880         /* [HGM] in protocol 1 we have to assume all variants valid */
8881         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8882         DisplayFatalError(buf, 0, 1);
8883         return;
8884       }
8885
8886       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8887       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8888       if( gameInfo.variant == VariantXiangqi )
8889            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8890       if( gameInfo.variant == VariantShogi )
8891            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8892       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8893            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8894       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8895                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8896            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8897       if( gameInfo.variant == VariantCourier )
8898            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8899       if( gameInfo.variant == VariantSuper )
8900            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8901       if( gameInfo.variant == VariantGreat )
8902            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8903       if( gameInfo.variant == VariantSChess )
8904            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8905
8906       if(overruled) {
8907         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8908                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8909            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8910            if(StrStr(cps->variants, b) == NULL) {
8911                // specific sized variant not known, check if general sizing allowed
8912                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8913                    if(StrStr(cps->variants, "boardsize") == NULL) {
8914                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8915                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8916                        DisplayFatalError(buf, 0, 1);
8917                        return;
8918                    }
8919                    /* [HGM] here we really should compare with the maximum supported board size */
8920                }
8921            }
8922       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8923       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8924       SendToProgram(buf, cps);
8925     }
8926     currentlyInitializedVariant = gameInfo.variant;
8927
8928     /* [HGM] send opening position in FRC to first engine */
8929     if(setup) {
8930           SendToProgram("force\n", cps);
8931           SendBoard(cps, 0);
8932           /* engine is now in force mode! Set flag to wake it up after first move. */
8933           setboardSpoiledMachineBlack = 1;
8934     }
8935
8936     if (cps->sendICS) {
8937       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8938       SendToProgram(buf, cps);
8939     }
8940     cps->maybeThinking = FALSE;
8941     cps->offeredDraw = 0;
8942     if (!appData.icsActive) {
8943         SendTimeControl(cps, movesPerSession, timeControl,
8944                         timeIncrement, appData.searchDepth,
8945                         searchTime);
8946     }
8947     if (appData.showThinking
8948         // [HGM] thinking: four options require thinking output to be sent
8949         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8950                                 ) {
8951         SendToProgram("post\n", cps);
8952     }
8953     SendToProgram("hard\n", cps);
8954     if (!appData.ponderNextMove) {
8955         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8956            it without being sure what state we are in first.  "hard"
8957            is not a toggle, so that one is OK.
8958          */
8959         SendToProgram("easy\n", cps);
8960     }
8961     if (cps->usePing) {
8962       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8963       SendToProgram(buf, cps);
8964     }
8965     cps->initDone = TRUE;
8966 }
8967
8968
8969 void
8970 StartChessProgram(cps)
8971      ChessProgramState *cps;
8972 {
8973     char buf[MSG_SIZ];
8974     int err;
8975
8976     if (appData.noChessProgram) return;
8977     cps->initDone = FALSE;
8978
8979     if (strcmp(cps->host, "localhost") == 0) {
8980         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8981     } else if (*appData.remoteShell == NULLCHAR) {
8982         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8983     } else {
8984         if (*appData.remoteUser == NULLCHAR) {
8985           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8986                     cps->program);
8987         } else {
8988           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8989                     cps->host, appData.remoteUser, cps->program);
8990         }
8991         err = StartChildProcess(buf, "", &cps->pr);
8992     }
8993
8994     if (err != 0) {
8995       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8996         DisplayFatalError(buf, err, 1);
8997         cps->pr = NoProc;
8998         cps->isr = NULL;
8999         return;
9000     }
9001
9002     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9003     if (cps->protocolVersion > 1) {
9004       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9005       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9006       cps->comboCnt = 0;  //                and values of combo boxes
9007       SendToProgram(buf, cps);
9008     } else {
9009       SendToProgram("xboard\n", cps);
9010     }
9011 }
9012
9013
9014 void
9015 TwoMachinesEventIfReady P((void))
9016 {
9017   if (first.lastPing != first.lastPong) {
9018     DisplayMessage("", _("Waiting for first chess program"));
9019     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9020     return;
9021   }
9022   if (second.lastPing != second.lastPong) {
9023     DisplayMessage("", _("Waiting for second chess program"));
9024     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9025     return;
9026   }
9027   ThawUI();
9028   TwoMachinesEvent();
9029 }
9030
9031 void
9032 NextMatchGame P((void))
9033 {
9034     int index; /* [HGM] autoinc: step load index during match */
9035     Reset(FALSE, TRUE);
9036     if (*appData.loadGameFile != NULLCHAR) {
9037         index = appData.loadGameIndex;
9038         if(index < 0) { // [HGM] autoinc
9039             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9040             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9041         }
9042         LoadGameFromFile(appData.loadGameFile,
9043                          index,
9044                          appData.loadGameFile, FALSE);
9045     } else if (*appData.loadPositionFile != NULLCHAR) {
9046         index = appData.loadPositionIndex;
9047         if(index < 0) { // [HGM] autoinc
9048             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9049             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9050         }
9051         LoadPositionFromFile(appData.loadPositionFile,
9052                              index,
9053                              appData.loadPositionFile);
9054     }
9055     TwoMachinesEventIfReady();
9056 }
9057
9058 void UserAdjudicationEvent( int result )
9059 {
9060     ChessMove gameResult = GameIsDrawn;
9061
9062     if( result > 0 ) {
9063         gameResult = WhiteWins;
9064     }
9065     else if( result < 0 ) {
9066         gameResult = BlackWins;
9067     }
9068
9069     if( gameMode == TwoMachinesPlay ) {
9070         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9071     }
9072 }
9073
9074
9075 // [HGM] save: calculate checksum of game to make games easily identifiable
9076 int StringCheckSum(char *s)
9077 {
9078         int i = 0;
9079         if(s==NULL) return 0;
9080         while(*s) i = i*259 + *s++;
9081         return i;
9082 }
9083
9084 int GameCheckSum()
9085 {
9086         int i, sum=0;
9087         for(i=backwardMostMove; i<forwardMostMove; i++) {
9088                 sum += pvInfoList[i].depth;
9089                 sum += StringCheckSum(parseList[i]);
9090                 sum += StringCheckSum(commentList[i]);
9091                 sum *= 261;
9092         }
9093         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9094         return sum + StringCheckSum(commentList[i]);
9095 } // end of save patch
9096
9097 void
9098 GameEnds(result, resultDetails, whosays)
9099      ChessMove result;
9100      char *resultDetails;
9101      int whosays;
9102 {
9103     GameMode nextGameMode;
9104     int isIcsGame;
9105     char buf[MSG_SIZ], popupRequested = 0;
9106
9107     if(endingGame) return; /* [HGM] crash: forbid recursion */
9108     endingGame = 1;
9109     if(twoBoards) { // [HGM] dual: switch back to one board
9110         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9111         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9112     }
9113     if (appData.debugMode) {
9114       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9115               result, resultDetails ? resultDetails : "(null)", whosays);
9116     }
9117
9118     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9119
9120     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9121         /* If we are playing on ICS, the server decides when the
9122            game is over, but the engine can offer to draw, claim
9123            a draw, or resign.
9124          */
9125 #if ZIPPY
9126         if (appData.zippyPlay && first.initDone) {
9127             if (result == GameIsDrawn) {
9128                 /* In case draw still needs to be claimed */
9129                 SendToICS(ics_prefix);
9130                 SendToICS("draw\n");
9131             } else if (StrCaseStr(resultDetails, "resign")) {
9132                 SendToICS(ics_prefix);
9133                 SendToICS("resign\n");
9134             }
9135         }
9136 #endif
9137         endingGame = 0; /* [HGM] crash */
9138         return;
9139     }
9140
9141     /* If we're loading the game from a file, stop */
9142     if (whosays == GE_FILE) {
9143       (void) StopLoadGameTimer();
9144       gameFileFP = NULL;
9145     }
9146
9147     /* Cancel draw offers */
9148     first.offeredDraw = second.offeredDraw = 0;
9149
9150     /* If this is an ICS game, only ICS can really say it's done;
9151        if not, anyone can. */
9152     isIcsGame = (gameMode == IcsPlayingWhite ||
9153                  gameMode == IcsPlayingBlack ||
9154                  gameMode == IcsObserving    ||
9155                  gameMode == IcsExamining);
9156
9157     if (!isIcsGame || whosays == GE_ICS) {
9158         /* OK -- not an ICS game, or ICS said it was done */
9159         StopClocks();
9160         if (!isIcsGame && !appData.noChessProgram)
9161           SetUserThinkingEnables();
9162
9163         /* [HGM] if a machine claims the game end we verify this claim */
9164         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9165             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9166                 char claimer;
9167                 ChessMove trueResult = (ChessMove) -1;
9168
9169                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9170                                             first.twoMachinesColor[0] :
9171                                             second.twoMachinesColor[0] ;
9172
9173                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9174                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9175                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9176                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9177                 } else
9178                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9179                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9180                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9181                 } else
9182                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9183                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9184                 }
9185
9186                 // now verify win claims, but not in drop games, as we don't understand those yet
9187                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9188                                                  || gameInfo.variant == VariantGreat) &&
9189                     (result == WhiteWins && claimer == 'w' ||
9190                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9191                       if (appData.debugMode) {
9192                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9193                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9194                       }
9195                       if(result != trueResult) {
9196                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9197                               result = claimer == 'w' ? BlackWins : WhiteWins;
9198                               resultDetails = buf;
9199                       }
9200                 } else
9201                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9202                     && (forwardMostMove <= backwardMostMove ||
9203                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9204                         (claimer=='b')==(forwardMostMove&1))
9205                                                                                   ) {
9206                       /* [HGM] verify: draws that were not flagged are false claims */
9207                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9208                       result = claimer == 'w' ? BlackWins : WhiteWins;
9209                       resultDetails = buf;
9210                 }
9211                 /* (Claiming a loss is accepted no questions asked!) */
9212             }
9213             /* [HGM] bare: don't allow bare King to win */
9214             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9215                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9216                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9217                && result != GameIsDrawn)
9218             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9219                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9220                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9221                         if(p >= 0 && p <= (int)WhiteKing) k++;
9222                 }
9223                 if (appData.debugMode) {
9224                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9225                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9226                 }
9227                 if(k <= 1) {
9228                         result = GameIsDrawn;
9229                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9230                         resultDetails = buf;
9231                 }
9232             }
9233         }
9234
9235
9236         if(serverMoves != NULL && !loadFlag) { char c = '=';
9237             if(result==WhiteWins) c = '+';
9238             if(result==BlackWins) c = '-';
9239             if(resultDetails != NULL)
9240                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9241         }
9242         if (resultDetails != NULL) {
9243             gameInfo.result = result;
9244             gameInfo.resultDetails = StrSave(resultDetails);
9245
9246             /* display last move only if game was not loaded from file */
9247             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9248                 DisplayMove(currentMove - 1);
9249
9250             if (forwardMostMove != 0) {
9251                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9252                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9253                                                                 ) {
9254                     if (*appData.saveGameFile != NULLCHAR) {
9255                         SaveGameToFile(appData.saveGameFile, TRUE);
9256                     } else if (appData.autoSaveGames) {
9257                         AutoSaveGame();
9258                     }
9259                     if (*appData.savePositionFile != NULLCHAR) {
9260                         SavePositionToFile(appData.savePositionFile);
9261                     }
9262                 }
9263             }
9264
9265             /* Tell program how game ended in case it is learning */
9266             /* [HGM] Moved this to after saving the PGN, just in case */
9267             /* engine died and we got here through time loss. In that */
9268             /* case we will get a fatal error writing the pipe, which */
9269             /* would otherwise lose us the PGN.                       */
9270             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9271             /* output during GameEnds should never be fatal anymore   */
9272             if (gameMode == MachinePlaysWhite ||
9273                 gameMode == MachinePlaysBlack ||
9274                 gameMode == TwoMachinesPlay ||
9275                 gameMode == IcsPlayingWhite ||
9276                 gameMode == IcsPlayingBlack ||
9277                 gameMode == BeginningOfGame) {
9278                 char buf[MSG_SIZ];
9279                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9280                         resultDetails);
9281                 if (first.pr != NoProc) {
9282                     SendToProgram(buf, &first);
9283                 }
9284                 if (second.pr != NoProc &&
9285                     gameMode == TwoMachinesPlay) {
9286                     SendToProgram(buf, &second);
9287                 }
9288             }
9289         }
9290
9291         if (appData.icsActive) {
9292             if (appData.quietPlay &&
9293                 (gameMode == IcsPlayingWhite ||
9294                  gameMode == IcsPlayingBlack)) {
9295                 SendToICS(ics_prefix);
9296                 SendToICS("set shout 1\n");
9297             }
9298             nextGameMode = IcsIdle;
9299             ics_user_moved = FALSE;
9300             /* clean up premove.  It's ugly when the game has ended and the
9301              * premove highlights are still on the board.
9302              */
9303             if (gotPremove) {
9304               gotPremove = FALSE;
9305               ClearPremoveHighlights();
9306               DrawPosition(FALSE, boards[currentMove]);
9307             }
9308             if (whosays == GE_ICS) {
9309                 switch (result) {
9310                 case WhiteWins:
9311                     if (gameMode == IcsPlayingWhite)
9312                         PlayIcsWinSound();
9313                     else if(gameMode == IcsPlayingBlack)
9314                         PlayIcsLossSound();
9315                     break;
9316                 case BlackWins:
9317                     if (gameMode == IcsPlayingBlack)
9318                         PlayIcsWinSound();
9319                     else if(gameMode == IcsPlayingWhite)
9320                         PlayIcsLossSound();
9321                     break;
9322                 case GameIsDrawn:
9323                     PlayIcsDrawSound();
9324                     break;
9325                 default:
9326                     PlayIcsUnfinishedSound();
9327                 }
9328             }
9329         } else if (gameMode == EditGame ||
9330                    gameMode == PlayFromGameFile ||
9331                    gameMode == AnalyzeMode ||
9332                    gameMode == AnalyzeFile) {
9333             nextGameMode = gameMode;
9334         } else {
9335             nextGameMode = EndOfGame;
9336         }
9337         pausing = FALSE;
9338         ModeHighlight();
9339     } else {
9340         nextGameMode = gameMode;
9341     }
9342
9343     if (appData.noChessProgram) {
9344         gameMode = nextGameMode;
9345         ModeHighlight();
9346         endingGame = 0; /* [HGM] crash */
9347         return;
9348     }
9349
9350     if (first.reuse) {
9351         /* Put first chess program into idle state */
9352         if (first.pr != NoProc &&
9353             (gameMode == MachinePlaysWhite ||
9354              gameMode == MachinePlaysBlack ||
9355              gameMode == TwoMachinesPlay ||
9356              gameMode == IcsPlayingWhite ||
9357              gameMode == IcsPlayingBlack ||
9358              gameMode == BeginningOfGame)) {
9359             SendToProgram("force\n", &first);
9360             if (first.usePing) {
9361               char buf[MSG_SIZ];
9362               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9363               SendToProgram(buf, &first);
9364             }
9365         }
9366     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9367         /* Kill off first chess program */
9368         if (first.isr != NULL)
9369           RemoveInputSource(first.isr);
9370         first.isr = NULL;
9371
9372         if (first.pr != NoProc) {
9373             ExitAnalyzeMode();
9374             DoSleep( appData.delayBeforeQuit );
9375             SendToProgram("quit\n", &first);
9376             DoSleep( appData.delayAfterQuit );
9377             DestroyChildProcess(first.pr, first.useSigterm);
9378         }
9379         first.pr = NoProc;
9380     }
9381     if (second.reuse) {
9382         /* Put second chess program into idle state */
9383         if (second.pr != NoProc &&
9384             gameMode == TwoMachinesPlay) {
9385             SendToProgram("force\n", &second);
9386             if (second.usePing) {
9387               char buf[MSG_SIZ];
9388               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9389               SendToProgram(buf, &second);
9390             }
9391         }
9392     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9393         /* Kill off second chess program */
9394         if (second.isr != NULL)
9395           RemoveInputSource(second.isr);
9396         second.isr = NULL;
9397
9398         if (second.pr != NoProc) {
9399             DoSleep( appData.delayBeforeQuit );
9400             SendToProgram("quit\n", &second);
9401             DoSleep( appData.delayAfterQuit );
9402             DestroyChildProcess(second.pr, second.useSigterm);
9403         }
9404         second.pr = NoProc;
9405     }
9406
9407     if (matchMode && gameMode == TwoMachinesPlay) {
9408         switch (result) {
9409         case WhiteWins:
9410           if (first.twoMachinesColor[0] == 'w') {
9411             first.matchWins++;
9412           } else {
9413             second.matchWins++;
9414           }
9415           break;
9416         case BlackWins:
9417           if (first.twoMachinesColor[0] == 'b') {
9418             first.matchWins++;
9419           } else {
9420             second.matchWins++;
9421           }
9422           break;
9423         default:
9424           break;
9425         }
9426         if (matchGame < appData.matchGames) {
9427             char *tmp;
9428             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9429                 tmp = first.twoMachinesColor;
9430                 first.twoMachinesColor = second.twoMachinesColor;
9431                 second.twoMachinesColor = tmp;
9432             }
9433             gameMode = nextGameMode;
9434             matchGame++;
9435             if(appData.matchPause>10000 || appData.matchPause<10)
9436                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9437             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9438             endingGame = 0; /* [HGM] crash */
9439             return;
9440         } else {
9441             gameMode = nextGameMode;
9442             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9443                      first.tidy, second.tidy,
9444                      first.matchWins, second.matchWins,
9445                      appData.matchGames - (first.matchWins + second.matchWins));
9446             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9447             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9448                 first.twoMachinesColor = "black\n";
9449                 second.twoMachinesColor = "white\n";
9450             } else {
9451                 first.twoMachinesColor = "white\n";
9452                 second.twoMachinesColor = "black\n";
9453             }
9454         }
9455     }
9456     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9457         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9458       ExitAnalyzeMode();
9459     gameMode = nextGameMode;
9460     ModeHighlight();
9461     endingGame = 0;  /* [HGM] crash */
9462     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9463       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9464         matchMode = FALSE; appData.matchGames = matchGame = 0;
9465         DisplayNote(buf);
9466       }
9467     }
9468 }
9469
9470 /* Assumes program was just initialized (initString sent).
9471    Leaves program in force mode. */
9472 void
9473 FeedMovesToProgram(cps, upto)
9474      ChessProgramState *cps;
9475      int upto;
9476 {
9477     int i;
9478
9479     if (appData.debugMode)
9480       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9481               startedFromSetupPosition ? "position and " : "",
9482               backwardMostMove, upto, cps->which);
9483     if(currentlyInitializedVariant != gameInfo.variant) {
9484       char buf[MSG_SIZ];
9485         // [HGM] variantswitch: make engine aware of new variant
9486         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9487                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9488         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9489         SendToProgram(buf, cps);
9490         currentlyInitializedVariant = gameInfo.variant;
9491     }
9492     SendToProgram("force\n", cps);
9493     if (startedFromSetupPosition) {
9494         SendBoard(cps, backwardMostMove);
9495     if (appData.debugMode) {
9496         fprintf(debugFP, "feedMoves\n");
9497     }
9498     }
9499     for (i = backwardMostMove; i < upto; i++) {
9500         SendMoveToProgram(i, cps);
9501     }
9502 }
9503
9504
9505 void
9506 ResurrectChessProgram()
9507 {
9508      /* The chess program may have exited.
9509         If so, restart it and feed it all the moves made so far. */
9510
9511     if (appData.noChessProgram || first.pr != NoProc) return;
9512
9513     StartChessProgram(&first);
9514     InitChessProgram(&first, FALSE);
9515     FeedMovesToProgram(&first, currentMove);
9516
9517     if (!first.sendTime) {
9518         /* can't tell gnuchess what its clock should read,
9519            so we bow to its notion. */
9520         ResetClocks();
9521         timeRemaining[0][currentMove] = whiteTimeRemaining;
9522         timeRemaining[1][currentMove] = blackTimeRemaining;
9523     }
9524
9525     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9526                 appData.icsEngineAnalyze) && first.analysisSupport) {
9527       SendToProgram("analyze\n", &first);
9528       first.analyzing = TRUE;
9529     }
9530 }
9531
9532 /*
9533  * Button procedures
9534  */
9535 void
9536 Reset(redraw, init)
9537      int redraw, init;
9538 {
9539     int i;
9540
9541     if (appData.debugMode) {
9542         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9543                 redraw, init, gameMode);
9544     }
9545     CleanupTail(); // [HGM] vari: delete any stored variations
9546     pausing = pauseExamInvalid = FALSE;
9547     startedFromSetupPosition = blackPlaysFirst = FALSE;
9548     firstMove = TRUE;
9549     whiteFlag = blackFlag = FALSE;
9550     userOfferedDraw = FALSE;
9551     hintRequested = bookRequested = FALSE;
9552     first.maybeThinking = FALSE;
9553     second.maybeThinking = FALSE;
9554     first.bookSuspend = FALSE; // [HGM] book
9555     second.bookSuspend = FALSE;
9556     thinkOutput[0] = NULLCHAR;
9557     lastHint[0] = NULLCHAR;
9558     ClearGameInfo(&gameInfo);
9559     gameInfo.variant = StringToVariant(appData.variant);
9560     ics_user_moved = ics_clock_paused = FALSE;
9561     ics_getting_history = H_FALSE;
9562     ics_gamenum = -1;
9563     white_holding[0] = black_holding[0] = NULLCHAR;
9564     ClearProgramStats();
9565     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9566
9567     ResetFrontEnd();
9568     ClearHighlights();
9569     flipView = appData.flipView;
9570     ClearPremoveHighlights();
9571     gotPremove = FALSE;
9572     alarmSounded = FALSE;
9573
9574     GameEnds(EndOfFile, NULL, GE_PLAYER);
9575     if(appData.serverMovesName != NULL) {
9576         /* [HGM] prepare to make moves file for broadcasting */
9577         clock_t t = clock();
9578         if(serverMoves != NULL) fclose(serverMoves);
9579         serverMoves = fopen(appData.serverMovesName, "r");
9580         if(serverMoves != NULL) {
9581             fclose(serverMoves);
9582             /* delay 15 sec before overwriting, so all clients can see end */
9583             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9584         }
9585         serverMoves = fopen(appData.serverMovesName, "w");
9586     }
9587
9588     ExitAnalyzeMode();
9589     gameMode = BeginningOfGame;
9590     ModeHighlight();
9591     if(appData.icsActive) gameInfo.variant = VariantNormal;
9592     currentMove = forwardMostMove = backwardMostMove = 0;
9593     InitPosition(redraw);
9594     for (i = 0; i < MAX_MOVES; i++) {
9595         if (commentList[i] != NULL) {
9596             free(commentList[i]);
9597             commentList[i] = NULL;
9598         }
9599     }
9600     ResetClocks();
9601     timeRemaining[0][0] = whiteTimeRemaining;
9602     timeRemaining[1][0] = blackTimeRemaining;
9603     if (first.pr == NULL) {
9604         StartChessProgram(&first);
9605     }
9606     if (init) {
9607             InitChessProgram(&first, startedFromSetupPosition);
9608     }
9609     DisplayTitle("");
9610     DisplayMessage("", "");
9611     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9612     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9613 }
9614
9615 void
9616 AutoPlayGameLoop()
9617 {
9618     for (;;) {
9619         if (!AutoPlayOneMove())
9620           return;
9621         if (matchMode || appData.timeDelay == 0)
9622           continue;
9623         if (appData.timeDelay < 0)
9624           return;
9625         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9626         break;
9627     }
9628 }
9629
9630
9631 int
9632 AutoPlayOneMove()
9633 {
9634     int fromX, fromY, toX, toY;
9635
9636     if (appData.debugMode) {
9637       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9638     }
9639
9640     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9641       return FALSE;
9642
9643     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9644       pvInfoList[currentMove].depth = programStats.depth;
9645       pvInfoList[currentMove].score = programStats.score;
9646       pvInfoList[currentMove].time  = 0;
9647       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9648     }
9649
9650     if (currentMove >= forwardMostMove) {
9651       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9652       gameMode = EditGame;
9653       ModeHighlight();
9654
9655       /* [AS] Clear current move marker at the end of a game */
9656       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9657
9658       return FALSE;
9659     }
9660
9661     toX = moveList[currentMove][2] - AAA;
9662     toY = moveList[currentMove][3] - ONE;
9663
9664     if (moveList[currentMove][1] == '@') {
9665         if (appData.highlightLastMove) {
9666             SetHighlights(-1, -1, toX, toY);
9667         }
9668     } else {
9669         fromX = moveList[currentMove][0] - AAA;
9670         fromY = moveList[currentMove][1] - ONE;
9671
9672         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9673
9674         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9675
9676         if (appData.highlightLastMove) {
9677             SetHighlights(fromX, fromY, toX, toY);
9678         }
9679     }
9680     DisplayMove(currentMove);
9681     SendMoveToProgram(currentMove++, &first);
9682     DisplayBothClocks();
9683     DrawPosition(FALSE, boards[currentMove]);
9684     // [HGM] PV info: always display, routine tests if empty
9685     DisplayComment(currentMove - 1, commentList[currentMove]);
9686     return TRUE;
9687 }
9688
9689
9690 int
9691 LoadGameOneMove(readAhead)
9692      ChessMove readAhead;
9693 {
9694     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9695     char promoChar = NULLCHAR;
9696     ChessMove moveType;
9697     char move[MSG_SIZ];
9698     char *p, *q;
9699
9700     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9701         gameMode != AnalyzeMode && gameMode != Training) {
9702         gameFileFP = NULL;
9703         return FALSE;
9704     }
9705
9706     yyboardindex = forwardMostMove;
9707     if (readAhead != EndOfFile) {
9708       moveType = readAhead;
9709     } else {
9710       if (gameFileFP == NULL)
9711           return FALSE;
9712       moveType = (ChessMove) Myylex();
9713     }
9714
9715     done = FALSE;
9716     switch (moveType) {
9717       case Comment:
9718         if (appData.debugMode)
9719           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9720         p = yy_text;
9721
9722         /* append the comment but don't display it */
9723         AppendComment(currentMove, p, FALSE);
9724         return TRUE;
9725
9726       case WhiteCapturesEnPassant:
9727       case BlackCapturesEnPassant:
9728       case WhitePromotion:
9729       case BlackPromotion:
9730       case WhiteNonPromotion:
9731       case BlackNonPromotion:
9732       case NormalMove:
9733       case WhiteKingSideCastle:
9734       case WhiteQueenSideCastle:
9735       case BlackKingSideCastle:
9736       case BlackQueenSideCastle:
9737       case WhiteKingSideCastleWild:
9738       case WhiteQueenSideCastleWild:
9739       case BlackKingSideCastleWild:
9740       case BlackQueenSideCastleWild:
9741       /* PUSH Fabien */
9742       case WhiteHSideCastleFR:
9743       case WhiteASideCastleFR:
9744       case BlackHSideCastleFR:
9745       case BlackASideCastleFR:
9746       /* POP Fabien */
9747         if (appData.debugMode)
9748           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9749         fromX = currentMoveString[0] - AAA;
9750         fromY = currentMoveString[1] - ONE;
9751         toX = currentMoveString[2] - AAA;
9752         toY = currentMoveString[3] - ONE;
9753         promoChar = currentMoveString[4];
9754         break;
9755
9756       case WhiteDrop:
9757       case BlackDrop:
9758         if (appData.debugMode)
9759           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9760         fromX = moveType == WhiteDrop ?
9761           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9762         (int) CharToPiece(ToLower(currentMoveString[0]));
9763         fromY = DROP_RANK;
9764         toX = currentMoveString[2] - AAA;
9765         toY = currentMoveString[3] - ONE;
9766         break;
9767
9768       case WhiteWins:
9769       case BlackWins:
9770       case GameIsDrawn:
9771       case GameUnfinished:
9772         if (appData.debugMode)
9773           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9774         p = strchr(yy_text, '{');
9775         if (p == NULL) p = strchr(yy_text, '(');
9776         if (p == NULL) {
9777             p = yy_text;
9778             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9779         } else {
9780             q = strchr(p, *p == '{' ? '}' : ')');
9781             if (q != NULL) *q = NULLCHAR;
9782             p++;
9783         }
9784         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9785         GameEnds(moveType, p, GE_FILE);
9786         done = TRUE;
9787         if (cmailMsgLoaded) {
9788             ClearHighlights();
9789             flipView = WhiteOnMove(currentMove);
9790             if (moveType == GameUnfinished) flipView = !flipView;
9791             if (appData.debugMode)
9792               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9793         }
9794         break;
9795
9796       case EndOfFile:
9797         if (appData.debugMode)
9798           fprintf(debugFP, "Parser hit end of file\n");
9799         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9800           case MT_NONE:
9801           case MT_CHECK:
9802             break;
9803           case MT_CHECKMATE:
9804           case MT_STAINMATE:
9805             if (WhiteOnMove(currentMove)) {
9806                 GameEnds(BlackWins, "Black mates", GE_FILE);
9807             } else {
9808                 GameEnds(WhiteWins, "White mates", GE_FILE);
9809             }
9810             break;
9811           case MT_STALEMATE:
9812             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9813             break;
9814         }
9815         done = TRUE;
9816         break;
9817
9818       case MoveNumberOne:
9819         if (lastLoadGameStart == GNUChessGame) {
9820             /* GNUChessGames have numbers, but they aren't move numbers */
9821             if (appData.debugMode)
9822               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9823                       yy_text, (int) moveType);
9824             return LoadGameOneMove(EndOfFile); /* tail recursion */
9825         }
9826         /* else fall thru */
9827
9828       case XBoardGame:
9829       case GNUChessGame:
9830       case PGNTag:
9831         /* Reached start of next game in file */
9832         if (appData.debugMode)
9833           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9834         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9835           case MT_NONE:
9836           case MT_CHECK:
9837             break;
9838           case MT_CHECKMATE:
9839           case MT_STAINMATE:
9840             if (WhiteOnMove(currentMove)) {
9841                 GameEnds(BlackWins, "Black mates", GE_FILE);
9842             } else {
9843                 GameEnds(WhiteWins, "White mates", GE_FILE);
9844             }
9845             break;
9846           case MT_STALEMATE:
9847             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9848             break;
9849         }
9850         done = TRUE;
9851         break;
9852
9853       case PositionDiagram:     /* should not happen; ignore */
9854       case ElapsedTime:         /* ignore */
9855       case NAG:                 /* ignore */
9856         if (appData.debugMode)
9857           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9858                   yy_text, (int) moveType);
9859         return LoadGameOneMove(EndOfFile); /* tail recursion */
9860
9861       case IllegalMove:
9862         if (appData.testLegality) {
9863             if (appData.debugMode)
9864               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9865             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9866                     (forwardMostMove / 2) + 1,
9867                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9868             DisplayError(move, 0);
9869             done = TRUE;
9870         } else {
9871             if (appData.debugMode)
9872               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9873                       yy_text, currentMoveString);
9874             fromX = currentMoveString[0] - AAA;
9875             fromY = currentMoveString[1] - ONE;
9876             toX = currentMoveString[2] - AAA;
9877             toY = currentMoveString[3] - ONE;
9878             promoChar = currentMoveString[4];
9879         }
9880         break;
9881
9882       case AmbiguousMove:
9883         if (appData.debugMode)
9884           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9885         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9886                 (forwardMostMove / 2) + 1,
9887                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9888         DisplayError(move, 0);
9889         done = TRUE;
9890         break;
9891
9892       default:
9893       case ImpossibleMove:
9894         if (appData.debugMode)
9895           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9896         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9897                 (forwardMostMove / 2) + 1,
9898                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9899         DisplayError(move, 0);
9900         done = TRUE;
9901         break;
9902     }
9903
9904     if (done) {
9905         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9906             DrawPosition(FALSE, boards[currentMove]);
9907             DisplayBothClocks();
9908             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9909               DisplayComment(currentMove - 1, commentList[currentMove]);
9910         }
9911         (void) StopLoadGameTimer();
9912         gameFileFP = NULL;
9913         cmailOldMove = forwardMostMove;
9914         return FALSE;
9915     } else {
9916         /* currentMoveString is set as a side-effect of yylex */
9917
9918         thinkOutput[0] = NULLCHAR;
9919         MakeMove(fromX, fromY, toX, toY, promoChar);
9920         currentMove = forwardMostMove;
9921         return TRUE;
9922     }
9923 }
9924
9925 /* Load the nth game from the given file */
9926 int
9927 LoadGameFromFile(filename, n, title, useList)
9928      char *filename;
9929      int n;
9930      char *title;
9931      /*Boolean*/ int useList;
9932 {
9933     FILE *f;
9934     char buf[MSG_SIZ];
9935
9936     if (strcmp(filename, "-") == 0) {
9937         f = stdin;
9938         title = "stdin";
9939     } else {
9940         f = fopen(filename, "rb");
9941         if (f == NULL) {
9942           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9943             DisplayError(buf, errno);
9944             return FALSE;
9945         }
9946     }
9947     if (fseek(f, 0, 0) == -1) {
9948         /* f is not seekable; probably a pipe */
9949         useList = FALSE;
9950     }
9951     if (useList && n == 0) {
9952         int error = GameListBuild(f);
9953         if (error) {
9954             DisplayError(_("Cannot build game list"), error);
9955         } else if (!ListEmpty(&gameList) &&
9956                    ((ListGame *) gameList.tailPred)->number > 1) {
9957             GameListPopUp(f, title);
9958             return TRUE;
9959         }
9960         GameListDestroy();
9961         n = 1;
9962     }
9963     if (n == 0) n = 1;
9964     return LoadGame(f, n, title, FALSE);
9965 }
9966
9967
9968 void
9969 MakeRegisteredMove()
9970 {
9971     int fromX, fromY, toX, toY;
9972     char promoChar;
9973     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9974         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9975           case CMAIL_MOVE:
9976           case CMAIL_DRAW:
9977             if (appData.debugMode)
9978               fprintf(debugFP, "Restoring %s for game %d\n",
9979                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9980
9981             thinkOutput[0] = NULLCHAR;
9982             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9983             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9984             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9985             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9986             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9987             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9988             MakeMove(fromX, fromY, toX, toY, promoChar);
9989             ShowMove(fromX, fromY, toX, toY);
9990
9991             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9992               case MT_NONE:
9993               case MT_CHECK:
9994                 break;
9995
9996               case MT_CHECKMATE:
9997               case MT_STAINMATE:
9998                 if (WhiteOnMove(currentMove)) {
9999                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10000                 } else {
10001                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10002                 }
10003                 break;
10004
10005               case MT_STALEMATE:
10006                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10007                 break;
10008             }
10009
10010             break;
10011
10012           case CMAIL_RESIGN:
10013             if (WhiteOnMove(currentMove)) {
10014                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10015             } else {
10016                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10017             }
10018             break;
10019
10020           case CMAIL_ACCEPT:
10021             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10022             break;
10023
10024           default:
10025             break;
10026         }
10027     }
10028
10029     return;
10030 }
10031
10032 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10033 int
10034 CmailLoadGame(f, gameNumber, title, useList)
10035      FILE *f;
10036      int gameNumber;
10037      char *title;
10038      int useList;
10039 {
10040     int retVal;
10041
10042     if (gameNumber > nCmailGames) {
10043         DisplayError(_("No more games in this message"), 0);
10044         return FALSE;
10045     }
10046     if (f == lastLoadGameFP) {
10047         int offset = gameNumber - lastLoadGameNumber;
10048         if (offset == 0) {
10049             cmailMsg[0] = NULLCHAR;
10050             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10051                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10052                 nCmailMovesRegistered--;
10053             }
10054             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10055             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10056                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10057             }
10058         } else {
10059             if (! RegisterMove()) return FALSE;
10060         }
10061     }
10062
10063     retVal = LoadGame(f, gameNumber, title, useList);
10064
10065     /* Make move registered during previous look at this game, if any */
10066     MakeRegisteredMove();
10067
10068     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10069         commentList[currentMove]
10070           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10071         DisplayComment(currentMove - 1, commentList[currentMove]);
10072     }
10073
10074     return retVal;
10075 }
10076
10077 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10078 int
10079 ReloadGame(offset)
10080      int offset;
10081 {
10082     int gameNumber = lastLoadGameNumber + offset;
10083     if (lastLoadGameFP == NULL) {
10084         DisplayError(_("No game has been loaded yet"), 0);
10085         return FALSE;
10086     }
10087     if (gameNumber <= 0) {
10088         DisplayError(_("Can't back up any further"), 0);
10089         return FALSE;
10090     }
10091     if (cmailMsgLoaded) {
10092         return CmailLoadGame(lastLoadGameFP, gameNumber,
10093                              lastLoadGameTitle, lastLoadGameUseList);
10094     } else {
10095         return LoadGame(lastLoadGameFP, gameNumber,
10096                         lastLoadGameTitle, lastLoadGameUseList);
10097     }
10098 }
10099
10100
10101
10102 /* Load the nth game from open file f */
10103 int
10104 LoadGame(f, gameNumber, title, useList)
10105      FILE *f;
10106      int gameNumber;
10107      char *title;
10108      int useList;
10109 {
10110     ChessMove cm;
10111     char buf[MSG_SIZ];
10112     int gn = gameNumber;
10113     ListGame *lg = NULL;
10114     int numPGNTags = 0;
10115     int err;
10116     GameMode oldGameMode;
10117     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10118
10119     if (appData.debugMode)
10120         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10121
10122     if (gameMode == Training )
10123         SetTrainingModeOff();
10124
10125     oldGameMode = gameMode;
10126     if (gameMode != BeginningOfGame) {
10127       Reset(FALSE, TRUE);
10128     }
10129
10130     gameFileFP = f;
10131     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10132         fclose(lastLoadGameFP);
10133     }
10134
10135     if (useList) {
10136         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10137
10138         if (lg) {
10139             fseek(f, lg->offset, 0);
10140             GameListHighlight(gameNumber);
10141             gn = 1;
10142         }
10143         else {
10144             DisplayError(_("Game number out of range"), 0);
10145             return FALSE;
10146         }
10147     } else {
10148         GameListDestroy();
10149         if (fseek(f, 0, 0) == -1) {
10150             if (f == lastLoadGameFP ?
10151                 gameNumber == lastLoadGameNumber + 1 :
10152                 gameNumber == 1) {
10153                 gn = 1;
10154             } else {
10155                 DisplayError(_("Can't seek on game file"), 0);
10156                 return FALSE;
10157             }
10158         }
10159     }
10160     lastLoadGameFP = f;
10161     lastLoadGameNumber = gameNumber;
10162     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10163     lastLoadGameUseList = useList;
10164
10165     yynewfile(f);
10166
10167     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10168       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10169                 lg->gameInfo.black);
10170             DisplayTitle(buf);
10171     } else if (*title != NULLCHAR) {
10172         if (gameNumber > 1) {
10173           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10174             DisplayTitle(buf);
10175         } else {
10176             DisplayTitle(title);
10177         }
10178     }
10179
10180     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10181         gameMode = PlayFromGameFile;
10182         ModeHighlight();
10183     }
10184
10185     currentMove = forwardMostMove = backwardMostMove = 0;
10186     CopyBoard(boards[0], initialPosition);
10187     StopClocks();
10188
10189     /*
10190      * Skip the first gn-1 games in the file.
10191      * Also skip over anything that precedes an identifiable
10192      * start of game marker, to avoid being confused by
10193      * garbage at the start of the file.  Currently
10194      * recognized start of game markers are the move number "1",
10195      * the pattern "gnuchess .* game", the pattern
10196      * "^[#;%] [^ ]* game file", and a PGN tag block.
10197      * A game that starts with one of the latter two patterns
10198      * will also have a move number 1, possibly
10199      * following a position diagram.
10200      * 5-4-02: Let's try being more lenient and allowing a game to
10201      * start with an unnumbered move.  Does that break anything?
10202      */
10203     cm = lastLoadGameStart = EndOfFile;
10204     while (gn > 0) {
10205         yyboardindex = forwardMostMove;
10206         cm = (ChessMove) Myylex();
10207         switch (cm) {
10208           case EndOfFile:
10209             if (cmailMsgLoaded) {
10210                 nCmailGames = CMAIL_MAX_GAMES - gn;
10211             } else {
10212                 Reset(TRUE, TRUE);
10213                 DisplayError(_("Game not found in file"), 0);
10214             }
10215             return FALSE;
10216
10217           case GNUChessGame:
10218           case XBoardGame:
10219             gn--;
10220             lastLoadGameStart = cm;
10221             break;
10222
10223           case MoveNumberOne:
10224             switch (lastLoadGameStart) {
10225               case GNUChessGame:
10226               case XBoardGame:
10227               case PGNTag:
10228                 break;
10229               case MoveNumberOne:
10230               case EndOfFile:
10231                 gn--;           /* count this game */
10232                 lastLoadGameStart = cm;
10233                 break;
10234               default:
10235                 /* impossible */
10236                 break;
10237             }
10238             break;
10239
10240           case PGNTag:
10241             switch (lastLoadGameStart) {
10242               case GNUChessGame:
10243               case PGNTag:
10244               case MoveNumberOne:
10245               case EndOfFile:
10246                 gn--;           /* count this game */
10247                 lastLoadGameStart = cm;
10248                 break;
10249               case XBoardGame:
10250                 lastLoadGameStart = cm; /* game counted already */
10251                 break;
10252               default:
10253                 /* impossible */
10254                 break;
10255             }
10256             if (gn > 0) {
10257                 do {
10258                     yyboardindex = forwardMostMove;
10259                     cm = (ChessMove) Myylex();
10260                 } while (cm == PGNTag || cm == Comment);
10261             }
10262             break;
10263
10264           case WhiteWins:
10265           case BlackWins:
10266           case GameIsDrawn:
10267             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10268                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10269                     != CMAIL_OLD_RESULT) {
10270                     nCmailResults ++ ;
10271                     cmailResult[  CMAIL_MAX_GAMES
10272                                 - gn - 1] = CMAIL_OLD_RESULT;
10273                 }
10274             }
10275             break;
10276
10277           case NormalMove:
10278             /* Only a NormalMove can be at the start of a game
10279              * without a position diagram. */
10280             if (lastLoadGameStart == EndOfFile ) {
10281               gn--;
10282               lastLoadGameStart = MoveNumberOne;
10283             }
10284             break;
10285
10286           default:
10287             break;
10288         }
10289     }
10290
10291     if (appData.debugMode)
10292       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10293
10294     if (cm == XBoardGame) {
10295         /* Skip any header junk before position diagram and/or move 1 */
10296         for (;;) {
10297             yyboardindex = forwardMostMove;
10298             cm = (ChessMove) Myylex();
10299
10300             if (cm == EndOfFile ||
10301                 cm == GNUChessGame || cm == XBoardGame) {
10302                 /* Empty game; pretend end-of-file and handle later */
10303                 cm = EndOfFile;
10304                 break;
10305             }
10306
10307             if (cm == MoveNumberOne || cm == PositionDiagram ||
10308                 cm == PGNTag || cm == Comment)
10309               break;
10310         }
10311     } else if (cm == GNUChessGame) {
10312         if (gameInfo.event != NULL) {
10313             free(gameInfo.event);
10314         }
10315         gameInfo.event = StrSave(yy_text);
10316     }
10317
10318     startedFromSetupPosition = FALSE;
10319     while (cm == PGNTag) {
10320         if (appData.debugMode)
10321           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10322         err = ParsePGNTag(yy_text, &gameInfo);
10323         if (!err) numPGNTags++;
10324
10325         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10326         if(gameInfo.variant != oldVariant) {
10327             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10328             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10329             InitPosition(TRUE);
10330             oldVariant = gameInfo.variant;
10331             if (appData.debugMode)
10332               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10333         }
10334
10335
10336         if (gameInfo.fen != NULL) {
10337           Board initial_position;
10338           startedFromSetupPosition = TRUE;
10339           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10340             Reset(TRUE, TRUE);
10341             DisplayError(_("Bad FEN position in file"), 0);
10342             return FALSE;
10343           }
10344           CopyBoard(boards[0], initial_position);
10345           if (blackPlaysFirst) {
10346             currentMove = forwardMostMove = backwardMostMove = 1;
10347             CopyBoard(boards[1], initial_position);
10348             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10349             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10350             timeRemaining[0][1] = whiteTimeRemaining;
10351             timeRemaining[1][1] = blackTimeRemaining;
10352             if (commentList[0] != NULL) {
10353               commentList[1] = commentList[0];
10354               commentList[0] = NULL;
10355             }
10356           } else {
10357             currentMove = forwardMostMove = backwardMostMove = 0;
10358           }
10359           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10360           {   int i;
10361               initialRulePlies = FENrulePlies;
10362               for( i=0; i< nrCastlingRights; i++ )
10363                   initialRights[i] = initial_position[CASTLING][i];
10364           }
10365           yyboardindex = forwardMostMove;
10366           free(gameInfo.fen);
10367           gameInfo.fen = NULL;
10368         }
10369
10370         yyboardindex = forwardMostMove;
10371         cm = (ChessMove) Myylex();
10372
10373         /* Handle comments interspersed among the tags */
10374         while (cm == Comment) {
10375             char *p;
10376             if (appData.debugMode)
10377               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10378             p = yy_text;
10379             AppendComment(currentMove, p, FALSE);
10380             yyboardindex = forwardMostMove;
10381             cm = (ChessMove) Myylex();
10382         }
10383     }
10384
10385     /* don't rely on existence of Event tag since if game was
10386      * pasted from clipboard the Event tag may not exist
10387      */
10388     if (numPGNTags > 0){
10389         char *tags;
10390         if (gameInfo.variant == VariantNormal) {
10391           VariantClass v = StringToVariant(gameInfo.event);
10392           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10393           if(v < VariantShogi) gameInfo.variant = v;
10394         }
10395         if (!matchMode) {
10396           if( appData.autoDisplayTags ) {
10397             tags = PGNTags(&gameInfo);
10398             TagsPopUp(tags, CmailMsg());
10399             free(tags);
10400           }
10401         }
10402     } else {
10403         /* Make something up, but don't display it now */
10404         SetGameInfo();
10405         TagsPopDown();
10406     }
10407
10408     if (cm == PositionDiagram) {
10409         int i, j;
10410         char *p;
10411         Board initial_position;
10412
10413         if (appData.debugMode)
10414           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10415
10416         if (!startedFromSetupPosition) {
10417             p = yy_text;
10418             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10419               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10420                 switch (*p) {
10421                   case '[':
10422                   case '-':
10423                   case ' ':
10424                   case '\t':
10425                   case '\n':
10426                   case '\r':
10427                     break;
10428                   default:
10429                     initial_position[i][j++] = CharToPiece(*p);
10430                     break;
10431                 }
10432             while (*p == ' ' || *p == '\t' ||
10433                    *p == '\n' || *p == '\r') p++;
10434
10435             if (strncmp(p, "black", strlen("black"))==0)
10436               blackPlaysFirst = TRUE;
10437             else
10438               blackPlaysFirst = FALSE;
10439             startedFromSetupPosition = TRUE;
10440
10441             CopyBoard(boards[0], initial_position);
10442             if (blackPlaysFirst) {
10443                 currentMove = forwardMostMove = backwardMostMove = 1;
10444                 CopyBoard(boards[1], initial_position);
10445                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10446                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10447                 timeRemaining[0][1] = whiteTimeRemaining;
10448                 timeRemaining[1][1] = blackTimeRemaining;
10449                 if (commentList[0] != NULL) {
10450                     commentList[1] = commentList[0];
10451                     commentList[0] = NULL;
10452                 }
10453             } else {
10454                 currentMove = forwardMostMove = backwardMostMove = 0;
10455             }
10456         }
10457         yyboardindex = forwardMostMove;
10458         cm = (ChessMove) Myylex();
10459     }
10460
10461     if (first.pr == NoProc) {
10462         StartChessProgram(&first);
10463     }
10464     InitChessProgram(&first, FALSE);
10465     SendToProgram("force\n", &first);
10466     if (startedFromSetupPosition) {
10467         SendBoard(&first, forwardMostMove);
10468     if (appData.debugMode) {
10469         fprintf(debugFP, "Load Game\n");
10470     }
10471         DisplayBothClocks();
10472     }
10473
10474     /* [HGM] server: flag to write setup moves in broadcast file as one */
10475     loadFlag = appData.suppressLoadMoves;
10476
10477     while (cm == Comment) {
10478         char *p;
10479         if (appData.debugMode)
10480           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10481         p = yy_text;
10482         AppendComment(currentMove, p, FALSE);
10483         yyboardindex = forwardMostMove;
10484         cm = (ChessMove) Myylex();
10485     }
10486
10487     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10488         cm == WhiteWins || cm == BlackWins ||
10489         cm == GameIsDrawn || cm == GameUnfinished) {
10490         DisplayMessage("", _("No moves in game"));
10491         if (cmailMsgLoaded) {
10492             if (appData.debugMode)
10493               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10494             ClearHighlights();
10495             flipView = FALSE;
10496         }
10497         DrawPosition(FALSE, boards[currentMove]);
10498         DisplayBothClocks();
10499         gameMode = EditGame;
10500         ModeHighlight();
10501         gameFileFP = NULL;
10502         cmailOldMove = 0;
10503         return TRUE;
10504     }
10505
10506     // [HGM] PV info: routine tests if comment empty
10507     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10508         DisplayComment(currentMove - 1, commentList[currentMove]);
10509     }
10510     if (!matchMode && appData.timeDelay != 0)
10511       DrawPosition(FALSE, boards[currentMove]);
10512
10513     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10514       programStats.ok_to_send = 1;
10515     }
10516
10517     /* if the first token after the PGN tags is a move
10518      * and not move number 1, retrieve it from the parser
10519      */
10520     if (cm != MoveNumberOne)
10521         LoadGameOneMove(cm);
10522
10523     /* load the remaining moves from the file */
10524     while (LoadGameOneMove(EndOfFile)) {
10525       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10526       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10527     }
10528
10529     /* rewind to the start of the game */
10530     currentMove = backwardMostMove;
10531
10532     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10533
10534     if (oldGameMode == AnalyzeFile ||
10535         oldGameMode == AnalyzeMode) {
10536       AnalyzeFileEvent();
10537     }
10538
10539     if (matchMode || appData.timeDelay == 0) {
10540       ToEndEvent();
10541       gameMode = EditGame;
10542       ModeHighlight();
10543     } else if (appData.timeDelay > 0) {
10544       AutoPlayGameLoop();
10545     }
10546
10547     if (appData.debugMode)
10548         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10549
10550     loadFlag = 0; /* [HGM] true game starts */
10551     return TRUE;
10552 }
10553
10554 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10555 int
10556 ReloadPosition(offset)
10557      int offset;
10558 {
10559     int positionNumber = lastLoadPositionNumber + offset;
10560     if (lastLoadPositionFP == NULL) {
10561         DisplayError(_("No position has been loaded yet"), 0);
10562         return FALSE;
10563     }
10564     if (positionNumber <= 0) {
10565         DisplayError(_("Can't back up any further"), 0);
10566         return FALSE;
10567     }
10568     return LoadPosition(lastLoadPositionFP, positionNumber,
10569                         lastLoadPositionTitle);
10570 }
10571
10572 /* Load the nth position from the given file */
10573 int
10574 LoadPositionFromFile(filename, n, title)
10575      char *filename;
10576      int n;
10577      char *title;
10578 {
10579     FILE *f;
10580     char buf[MSG_SIZ];
10581
10582     if (strcmp(filename, "-") == 0) {
10583         return LoadPosition(stdin, n, "stdin");
10584     } else {
10585         f = fopen(filename, "rb");
10586         if (f == NULL) {
10587             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10588             DisplayError(buf, errno);
10589             return FALSE;
10590         } else {
10591             return LoadPosition(f, n, title);
10592         }
10593     }
10594 }
10595
10596 /* Load the nth position from the given open file, and close it */
10597 int
10598 LoadPosition(f, positionNumber, title)
10599      FILE *f;
10600      int positionNumber;
10601      char *title;
10602 {
10603     char *p, line[MSG_SIZ];
10604     Board initial_position;
10605     int i, j, fenMode, pn;
10606
10607     if (gameMode == Training )
10608         SetTrainingModeOff();
10609
10610     if (gameMode != BeginningOfGame) {
10611         Reset(FALSE, TRUE);
10612     }
10613     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10614         fclose(lastLoadPositionFP);
10615     }
10616     if (positionNumber == 0) positionNumber = 1;
10617     lastLoadPositionFP = f;
10618     lastLoadPositionNumber = positionNumber;
10619     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10620     if (first.pr == NoProc) {
10621       StartChessProgram(&first);
10622       InitChessProgram(&first, FALSE);
10623     }
10624     pn = positionNumber;
10625     if (positionNumber < 0) {
10626         /* Negative position number means to seek to that byte offset */
10627         if (fseek(f, -positionNumber, 0) == -1) {
10628             DisplayError(_("Can't seek on position file"), 0);
10629             return FALSE;
10630         };
10631         pn = 1;
10632     } else {
10633         if (fseek(f, 0, 0) == -1) {
10634             if (f == lastLoadPositionFP ?
10635                 positionNumber == lastLoadPositionNumber + 1 :
10636                 positionNumber == 1) {
10637                 pn = 1;
10638             } else {
10639                 DisplayError(_("Can't seek on position file"), 0);
10640                 return FALSE;
10641             }
10642         }
10643     }
10644     /* See if this file is FEN or old-style xboard */
10645     if (fgets(line, MSG_SIZ, f) == NULL) {
10646         DisplayError(_("Position not found in file"), 0);
10647         return FALSE;
10648     }
10649     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10650     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10651
10652     if (pn >= 2) {
10653         if (fenMode || line[0] == '#') pn--;
10654         while (pn > 0) {
10655             /* skip positions before number pn */
10656             if (fgets(line, MSG_SIZ, f) == NULL) {
10657                 Reset(TRUE, TRUE);
10658                 DisplayError(_("Position not found in file"), 0);
10659                 return FALSE;
10660             }
10661             if (fenMode || line[0] == '#') pn--;
10662         }
10663     }
10664
10665     if (fenMode) {
10666         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10667             DisplayError(_("Bad FEN position in file"), 0);
10668             return FALSE;
10669         }
10670     } else {
10671         (void) fgets(line, MSG_SIZ, f);
10672         (void) fgets(line, MSG_SIZ, f);
10673
10674         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10675             (void) fgets(line, MSG_SIZ, f);
10676             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10677                 if (*p == ' ')
10678                   continue;
10679                 initial_position[i][j++] = CharToPiece(*p);
10680             }
10681         }
10682
10683         blackPlaysFirst = FALSE;
10684         if (!feof(f)) {
10685             (void) fgets(line, MSG_SIZ, f);
10686             if (strncmp(line, "black", strlen("black"))==0)
10687               blackPlaysFirst = TRUE;
10688         }
10689     }
10690     startedFromSetupPosition = TRUE;
10691
10692     SendToProgram("force\n", &first);
10693     CopyBoard(boards[0], initial_position);
10694     if (blackPlaysFirst) {
10695         currentMove = forwardMostMove = backwardMostMove = 1;
10696         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10697         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10698         CopyBoard(boards[1], initial_position);
10699         DisplayMessage("", _("Black to play"));
10700     } else {
10701         currentMove = forwardMostMove = backwardMostMove = 0;
10702         DisplayMessage("", _("White to play"));
10703     }
10704     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10705     SendBoard(&first, forwardMostMove);
10706     if (appData.debugMode) {
10707 int i, j;
10708   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10709   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10710         fprintf(debugFP, "Load Position\n");
10711     }
10712
10713     if (positionNumber > 1) {
10714       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10715         DisplayTitle(line);
10716     } else {
10717         DisplayTitle(title);
10718     }
10719     gameMode = EditGame;
10720     ModeHighlight();
10721     ResetClocks();
10722     timeRemaining[0][1] = whiteTimeRemaining;
10723     timeRemaining[1][1] = blackTimeRemaining;
10724     DrawPosition(FALSE, boards[currentMove]);
10725
10726     return TRUE;
10727 }
10728
10729
10730 void
10731 CopyPlayerNameIntoFileName(dest, src)
10732      char **dest, *src;
10733 {
10734     while (*src != NULLCHAR && *src != ',') {
10735         if (*src == ' ') {
10736             *(*dest)++ = '_';
10737             src++;
10738         } else {
10739             *(*dest)++ = *src++;
10740         }
10741     }
10742 }
10743
10744 char *DefaultFileName(ext)
10745      char *ext;
10746 {
10747     static char def[MSG_SIZ];
10748     char *p;
10749
10750     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10751         p = def;
10752         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10753         *p++ = '-';
10754         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10755         *p++ = '.';
10756         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10757     } else {
10758         def[0] = NULLCHAR;
10759     }
10760     return def;
10761 }
10762
10763 /* Save the current game to the given file */
10764 int
10765 SaveGameToFile(filename, append)
10766      char *filename;
10767      int append;
10768 {
10769     FILE *f;
10770     char buf[MSG_SIZ];
10771
10772     if (strcmp(filename, "-") == 0) {
10773         return SaveGame(stdout, 0, NULL);
10774     } else {
10775         f = fopen(filename, append ? "a" : "w");
10776         if (f == NULL) {
10777             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10778             DisplayError(buf, errno);
10779             return FALSE;
10780         } else {
10781             return SaveGame(f, 0, NULL);
10782         }
10783     }
10784 }
10785
10786 char *
10787 SavePart(str)
10788      char *str;
10789 {
10790     static char buf[MSG_SIZ];
10791     char *p;
10792
10793     p = strchr(str, ' ');
10794     if (p == NULL) return str;
10795     strncpy(buf, str, p - str);
10796     buf[p - str] = NULLCHAR;
10797     return buf;
10798 }
10799
10800 #define PGN_MAX_LINE 75
10801
10802 #define PGN_SIDE_WHITE  0
10803 #define PGN_SIDE_BLACK  1
10804
10805 /* [AS] */
10806 static int FindFirstMoveOutOfBook( int side )
10807 {
10808     int result = -1;
10809
10810     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10811         int index = backwardMostMove;
10812         int has_book_hit = 0;
10813
10814         if( (index % 2) != side ) {
10815             index++;
10816         }
10817
10818         while( index < forwardMostMove ) {
10819             /* Check to see if engine is in book */
10820             int depth = pvInfoList[index].depth;
10821             int score = pvInfoList[index].score;
10822             int in_book = 0;
10823
10824             if( depth <= 2 ) {
10825                 in_book = 1;
10826             }
10827             else if( score == 0 && depth == 63 ) {
10828                 in_book = 1; /* Zappa */
10829             }
10830             else if( score == 2 && depth == 99 ) {
10831                 in_book = 1; /* Abrok */
10832             }
10833
10834             has_book_hit += in_book;
10835
10836             if( ! in_book ) {
10837                 result = index;
10838
10839                 break;
10840             }
10841
10842             index += 2;
10843         }
10844     }
10845
10846     return result;
10847 }
10848
10849 /* [AS] */
10850 void GetOutOfBookInfo( char * buf )
10851 {
10852     int oob[2];
10853     int i;
10854     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10855
10856     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10857     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10858
10859     *buf = '\0';
10860
10861     if( oob[0] >= 0 || oob[1] >= 0 ) {
10862         for( i=0; i<2; i++ ) {
10863             int idx = oob[i];
10864
10865             if( idx >= 0 ) {
10866                 if( i > 0 && oob[0] >= 0 ) {
10867                     strcat( buf, "   " );
10868                 }
10869
10870                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10871                 sprintf( buf+strlen(buf), "%s%.2f",
10872                     pvInfoList[idx].score >= 0 ? "+" : "",
10873                     pvInfoList[idx].score / 100.0 );
10874             }
10875         }
10876     }
10877 }
10878
10879 /* Save game in PGN style and close the file */
10880 int
10881 SaveGamePGN(f)
10882      FILE *f;
10883 {
10884     int i, offset, linelen, newblock;
10885     time_t tm;
10886 //    char *movetext;
10887     char numtext[32];
10888     int movelen, numlen, blank;
10889     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10890
10891     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10892
10893     tm = time((time_t *) NULL);
10894
10895     PrintPGNTags(f, &gameInfo);
10896
10897     if (backwardMostMove > 0 || startedFromSetupPosition) {
10898         char *fen = PositionToFEN(backwardMostMove, NULL);
10899         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10900         fprintf(f, "\n{--------------\n");
10901         PrintPosition(f, backwardMostMove);
10902         fprintf(f, "--------------}\n");
10903         free(fen);
10904     }
10905     else {
10906         /* [AS] Out of book annotation */
10907         if( appData.saveOutOfBookInfo ) {
10908             char buf[64];
10909
10910             GetOutOfBookInfo( buf );
10911
10912             if( buf[0] != '\0' ) {
10913                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10914             }
10915         }
10916
10917         fprintf(f, "\n");
10918     }
10919
10920     i = backwardMostMove;
10921     linelen = 0;
10922     newblock = TRUE;
10923
10924     while (i < forwardMostMove) {
10925         /* Print comments preceding this move */
10926         if (commentList[i] != NULL) {
10927             if (linelen > 0) fprintf(f, "\n");
10928             fprintf(f, "%s", commentList[i]);
10929             linelen = 0;
10930             newblock = TRUE;
10931         }
10932
10933         /* Format move number */
10934         if ((i % 2) == 0)
10935           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10936         else
10937           if (newblock)
10938             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10939           else
10940             numtext[0] = NULLCHAR;
10941
10942         numlen = strlen(numtext);
10943         newblock = FALSE;
10944
10945         /* Print move number */
10946         blank = linelen > 0 && numlen > 0;
10947         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10948             fprintf(f, "\n");
10949             linelen = 0;
10950             blank = 0;
10951         }
10952         if (blank) {
10953             fprintf(f, " ");
10954             linelen++;
10955         }
10956         fprintf(f, "%s", numtext);
10957         linelen += numlen;
10958
10959         /* Get move */
10960         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10961         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10962
10963         /* Print move */
10964         blank = linelen > 0 && movelen > 0;
10965         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10966             fprintf(f, "\n");
10967             linelen = 0;
10968             blank = 0;
10969         }
10970         if (blank) {
10971             fprintf(f, " ");
10972             linelen++;
10973         }
10974         fprintf(f, "%s", move_buffer);
10975         linelen += movelen;
10976
10977         /* [AS] Add PV info if present */
10978         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10979             /* [HGM] add time */
10980             char buf[MSG_SIZ]; int seconds;
10981
10982             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10983
10984             if( seconds <= 0)
10985               buf[0] = 0;
10986             else
10987               if( seconds < 30 )
10988                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10989               else
10990                 {
10991                   seconds = (seconds + 4)/10; // round to full seconds
10992                   if( seconds < 60 )
10993                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10994                   else
10995                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10996                 }
10997
10998             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10999                       pvInfoList[i].score >= 0 ? "+" : "",
11000                       pvInfoList[i].score / 100.0,
11001                       pvInfoList[i].depth,
11002                       buf );
11003
11004             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11005
11006             /* Print score/depth */
11007             blank = linelen > 0 && movelen > 0;
11008             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11009                 fprintf(f, "\n");
11010                 linelen = 0;
11011                 blank = 0;
11012             }
11013             if (blank) {
11014                 fprintf(f, " ");
11015                 linelen++;
11016             }
11017             fprintf(f, "%s", move_buffer);
11018             linelen += movelen;
11019         }
11020
11021         i++;
11022     }
11023
11024     /* Start a new line */
11025     if (linelen > 0) fprintf(f, "\n");
11026
11027     /* Print comments after last move */
11028     if (commentList[i] != NULL) {
11029         fprintf(f, "%s\n", commentList[i]);
11030     }
11031
11032     /* Print result */
11033     if (gameInfo.resultDetails != NULL &&
11034         gameInfo.resultDetails[0] != NULLCHAR) {
11035         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11036                 PGNResult(gameInfo.result));
11037     } else {
11038         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11039     }
11040
11041     fclose(f);
11042     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11043     return TRUE;
11044 }
11045
11046 /* Save game in old style and close the file */
11047 int
11048 SaveGameOldStyle(f)
11049      FILE *f;
11050 {
11051     int i, offset;
11052     time_t tm;
11053
11054     tm = time((time_t *) NULL);
11055
11056     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11057     PrintOpponents(f);
11058
11059     if (backwardMostMove > 0 || startedFromSetupPosition) {
11060         fprintf(f, "\n[--------------\n");
11061         PrintPosition(f, backwardMostMove);
11062         fprintf(f, "--------------]\n");
11063     } else {
11064         fprintf(f, "\n");
11065     }
11066
11067     i = backwardMostMove;
11068     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11069
11070     while (i < forwardMostMove) {
11071         if (commentList[i] != NULL) {
11072             fprintf(f, "[%s]\n", commentList[i]);
11073         }
11074
11075         if ((i % 2) == 1) {
11076             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11077             i++;
11078         } else {
11079             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11080             i++;
11081             if (commentList[i] != NULL) {
11082                 fprintf(f, "\n");
11083                 continue;
11084             }
11085             if (i >= forwardMostMove) {
11086                 fprintf(f, "\n");
11087                 break;
11088             }
11089             fprintf(f, "%s\n", parseList[i]);
11090             i++;
11091         }
11092     }
11093
11094     if (commentList[i] != NULL) {
11095         fprintf(f, "[%s]\n", commentList[i]);
11096     }
11097
11098     /* This isn't really the old style, but it's close enough */
11099     if (gameInfo.resultDetails != NULL &&
11100         gameInfo.resultDetails[0] != NULLCHAR) {
11101         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11102                 gameInfo.resultDetails);
11103     } else {
11104         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11105     }
11106
11107     fclose(f);
11108     return TRUE;
11109 }
11110
11111 /* Save the current game to open file f and close the file */
11112 int
11113 SaveGame(f, dummy, dummy2)
11114      FILE *f;
11115      int dummy;
11116      char *dummy2;
11117 {
11118     if (gameMode == EditPosition) EditPositionDone(TRUE);
11119     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11120     if (appData.oldSaveStyle)
11121       return SaveGameOldStyle(f);
11122     else
11123       return SaveGamePGN(f);
11124 }
11125
11126 /* Save the current position to the given file */
11127 int
11128 SavePositionToFile(filename)
11129      char *filename;
11130 {
11131     FILE *f;
11132     char buf[MSG_SIZ];
11133
11134     if (strcmp(filename, "-") == 0) {
11135         return SavePosition(stdout, 0, NULL);
11136     } else {
11137         f = fopen(filename, "a");
11138         if (f == NULL) {
11139             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11140             DisplayError(buf, errno);
11141             return FALSE;
11142         } else {
11143             SavePosition(f, 0, NULL);
11144             return TRUE;
11145         }
11146     }
11147 }
11148
11149 /* Save the current position to the given open file and close the file */
11150 int
11151 SavePosition(f, dummy, dummy2)
11152      FILE *f;
11153      int dummy;
11154      char *dummy2;
11155 {
11156     time_t tm;
11157     char *fen;
11158
11159     if (gameMode == EditPosition) EditPositionDone(TRUE);
11160     if (appData.oldSaveStyle) {
11161         tm = time((time_t *) NULL);
11162
11163         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11164         PrintOpponents(f);
11165         fprintf(f, "[--------------\n");
11166         PrintPosition(f, currentMove);
11167         fprintf(f, "--------------]\n");
11168     } else {
11169         fen = PositionToFEN(currentMove, NULL);
11170         fprintf(f, "%s\n", fen);
11171         free(fen);
11172     }
11173     fclose(f);
11174     return TRUE;
11175 }
11176
11177 void
11178 ReloadCmailMsgEvent(unregister)
11179      int unregister;
11180 {
11181 #if !WIN32
11182     static char *inFilename = NULL;
11183     static char *outFilename;
11184     int i;
11185     struct stat inbuf, outbuf;
11186     int status;
11187
11188     /* Any registered moves are unregistered if unregister is set, */
11189     /* i.e. invoked by the signal handler */
11190     if (unregister) {
11191         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11192             cmailMoveRegistered[i] = FALSE;
11193             if (cmailCommentList[i] != NULL) {
11194                 free(cmailCommentList[i]);
11195                 cmailCommentList[i] = NULL;
11196             }
11197         }
11198         nCmailMovesRegistered = 0;
11199     }
11200
11201     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11202         cmailResult[i] = CMAIL_NOT_RESULT;
11203     }
11204     nCmailResults = 0;
11205
11206     if (inFilename == NULL) {
11207         /* Because the filenames are static they only get malloced once  */
11208         /* and they never get freed                                      */
11209         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11210         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11211
11212         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11213         sprintf(outFilename, "%s.out", appData.cmailGameName);
11214     }
11215
11216     status = stat(outFilename, &outbuf);
11217     if (status < 0) {
11218         cmailMailedMove = FALSE;
11219     } else {
11220         status = stat(inFilename, &inbuf);
11221         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11222     }
11223
11224     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11225        counts the games, notes how each one terminated, etc.
11226
11227        It would be nice to remove this kludge and instead gather all
11228        the information while building the game list.  (And to keep it
11229        in the game list nodes instead of having a bunch of fixed-size
11230        parallel arrays.)  Note this will require getting each game's
11231        termination from the PGN tags, as the game list builder does
11232        not process the game moves.  --mann
11233        */
11234     cmailMsgLoaded = TRUE;
11235     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11236
11237     /* Load first game in the file or popup game menu */
11238     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11239
11240 #endif /* !WIN32 */
11241     return;
11242 }
11243
11244 int
11245 RegisterMove()
11246 {
11247     FILE *f;
11248     char string[MSG_SIZ];
11249
11250     if (   cmailMailedMove
11251         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11252         return TRUE;            /* Allow free viewing  */
11253     }
11254
11255     /* Unregister move to ensure that we don't leave RegisterMove        */
11256     /* with the move registered when the conditions for registering no   */
11257     /* longer hold                                                       */
11258     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11259         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11260         nCmailMovesRegistered --;
11261
11262         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11263           {
11264               free(cmailCommentList[lastLoadGameNumber - 1]);
11265               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11266           }
11267     }
11268
11269     if (cmailOldMove == -1) {
11270         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11271         return FALSE;
11272     }
11273
11274     if (currentMove > cmailOldMove + 1) {
11275         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11276         return FALSE;
11277     }
11278
11279     if (currentMove < cmailOldMove) {
11280         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11281         return FALSE;
11282     }
11283
11284     if (forwardMostMove > currentMove) {
11285         /* Silently truncate extra moves */
11286         TruncateGame();
11287     }
11288
11289     if (   (currentMove == cmailOldMove + 1)
11290         || (   (currentMove == cmailOldMove)
11291             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11292                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11293         if (gameInfo.result != GameUnfinished) {
11294             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11295         }
11296
11297         if (commentList[currentMove] != NULL) {
11298             cmailCommentList[lastLoadGameNumber - 1]
11299               = StrSave(commentList[currentMove]);
11300         }
11301         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11302
11303         if (appData.debugMode)
11304           fprintf(debugFP, "Saving %s for game %d\n",
11305                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11306
11307         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11308
11309         f = fopen(string, "w");
11310         if (appData.oldSaveStyle) {
11311             SaveGameOldStyle(f); /* also closes the file */
11312
11313             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11314             f = fopen(string, "w");
11315             SavePosition(f, 0, NULL); /* also closes the file */
11316         } else {
11317             fprintf(f, "{--------------\n");
11318             PrintPosition(f, currentMove);
11319             fprintf(f, "--------------}\n\n");
11320
11321             SaveGame(f, 0, NULL); /* also closes the file*/
11322         }
11323
11324         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11325         nCmailMovesRegistered ++;
11326     } else if (nCmailGames == 1) {
11327         DisplayError(_("You have not made a move yet"), 0);
11328         return FALSE;
11329     }
11330
11331     return TRUE;
11332 }
11333
11334 void
11335 MailMoveEvent()
11336 {
11337 #if !WIN32
11338     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11339     FILE *commandOutput;
11340     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11341     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11342     int nBuffers;
11343     int i;
11344     int archived;
11345     char *arcDir;
11346
11347     if (! cmailMsgLoaded) {
11348         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11349         return;
11350     }
11351
11352     if (nCmailGames == nCmailResults) {
11353         DisplayError(_("No unfinished games"), 0);
11354         return;
11355     }
11356
11357 #if CMAIL_PROHIBIT_REMAIL
11358     if (cmailMailedMove) {
11359       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);
11360         DisplayError(msg, 0);
11361         return;
11362     }
11363 #endif
11364
11365     if (! (cmailMailedMove || RegisterMove())) return;
11366
11367     if (   cmailMailedMove
11368         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11369       snprintf(string, MSG_SIZ, partCommandString,
11370                appData.debugMode ? " -v" : "", appData.cmailGameName);
11371         commandOutput = popen(string, "r");
11372
11373         if (commandOutput == NULL) {
11374             DisplayError(_("Failed to invoke cmail"), 0);
11375         } else {
11376             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11377                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11378             }
11379             if (nBuffers > 1) {
11380                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11381                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11382                 nBytes = MSG_SIZ - 1;
11383             } else {
11384                 (void) memcpy(msg, buffer, nBytes);
11385             }
11386             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11387
11388             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11389                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11390
11391                 archived = TRUE;
11392                 for (i = 0; i < nCmailGames; i ++) {
11393                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11394                         archived = FALSE;
11395                     }
11396                 }
11397                 if (   archived
11398                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11399                         != NULL)) {
11400                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11401                            arcDir,
11402                            appData.cmailGameName,
11403                            gameInfo.date);
11404                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11405                     cmailMsgLoaded = FALSE;
11406                 }
11407             }
11408
11409             DisplayInformation(msg);
11410             pclose(commandOutput);
11411         }
11412     } else {
11413         if ((*cmailMsg) != '\0') {
11414             DisplayInformation(cmailMsg);
11415         }
11416     }
11417
11418     return;
11419 #endif /* !WIN32 */
11420 }
11421
11422 char *
11423 CmailMsg()
11424 {
11425 #if WIN32
11426     return NULL;
11427 #else
11428     int  prependComma = 0;
11429     char number[5];
11430     char string[MSG_SIZ];       /* Space for game-list */
11431     int  i;
11432
11433     if (!cmailMsgLoaded) return "";
11434
11435     if (cmailMailedMove) {
11436       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11437     } else {
11438         /* Create a list of games left */
11439       snprintf(string, MSG_SIZ, "[");
11440         for (i = 0; i < nCmailGames; i ++) {
11441             if (! (   cmailMoveRegistered[i]
11442                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11443                 if (prependComma) {
11444                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11445                 } else {
11446                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11447                     prependComma = 1;
11448                 }
11449
11450                 strcat(string, number);
11451             }
11452         }
11453         strcat(string, "]");
11454
11455         if (nCmailMovesRegistered + nCmailResults == 0) {
11456             switch (nCmailGames) {
11457               case 1:
11458                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11459                 break;
11460
11461               case 2:
11462                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11463                 break;
11464
11465               default:
11466                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11467                          nCmailGames);
11468                 break;
11469             }
11470         } else {
11471             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11472               case 1:
11473                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11474                          string);
11475                 break;
11476
11477               case 0:
11478                 if (nCmailResults == nCmailGames) {
11479                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11480                 } else {
11481                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11482                 }
11483                 break;
11484
11485               default:
11486                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11487                          string);
11488             }
11489         }
11490     }
11491     return cmailMsg;
11492 #endif /* WIN32 */
11493 }
11494
11495 void
11496 ResetGameEvent()
11497 {
11498     if (gameMode == Training)
11499       SetTrainingModeOff();
11500
11501     Reset(TRUE, TRUE);
11502     cmailMsgLoaded = FALSE;
11503     if (appData.icsActive) {
11504       SendToICS(ics_prefix);
11505       SendToICS("refresh\n");
11506     }
11507 }
11508
11509 void
11510 ExitEvent(status)
11511      int status;
11512 {
11513     exiting++;
11514     if (exiting > 2) {
11515       /* Give up on clean exit */
11516       exit(status);
11517     }
11518     if (exiting > 1) {
11519       /* Keep trying for clean exit */
11520       return;
11521     }
11522
11523     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11524
11525     if (telnetISR != NULL) {
11526       RemoveInputSource(telnetISR);
11527     }
11528     if (icsPR != NoProc) {
11529       DestroyChildProcess(icsPR, TRUE);
11530     }
11531
11532     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11533     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11534
11535     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11536     /* make sure this other one finishes before killing it!                  */
11537     if(endingGame) { int count = 0;
11538         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11539         while(endingGame && count++ < 10) DoSleep(1);
11540         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11541     }
11542
11543     /* Kill off chess programs */
11544     if (first.pr != NoProc) {
11545         ExitAnalyzeMode();
11546
11547         DoSleep( appData.delayBeforeQuit );
11548         SendToProgram("quit\n", &first);
11549         DoSleep( appData.delayAfterQuit );
11550         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11551     }
11552     if (second.pr != NoProc) {
11553         DoSleep( appData.delayBeforeQuit );
11554         SendToProgram("quit\n", &second);
11555         DoSleep( appData.delayAfterQuit );
11556         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11557     }
11558     if (first.isr != NULL) {
11559         RemoveInputSource(first.isr);
11560     }
11561     if (second.isr != NULL) {
11562         RemoveInputSource(second.isr);
11563     }
11564
11565     ShutDownFrontEnd();
11566     exit(status);
11567 }
11568
11569 void
11570 PauseEvent()
11571 {
11572     if (appData.debugMode)
11573         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11574     if (pausing) {
11575         pausing = FALSE;
11576         ModeHighlight();
11577         if (gameMode == MachinePlaysWhite ||
11578             gameMode == MachinePlaysBlack) {
11579             StartClocks();
11580         } else {
11581             DisplayBothClocks();
11582         }
11583         if (gameMode == PlayFromGameFile) {
11584             if (appData.timeDelay >= 0)
11585                 AutoPlayGameLoop();
11586         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11587             Reset(FALSE, TRUE);
11588             SendToICS(ics_prefix);
11589             SendToICS("refresh\n");
11590         } else if (currentMove < forwardMostMove) {
11591             ForwardInner(forwardMostMove);
11592         }
11593         pauseExamInvalid = FALSE;
11594     } else {
11595         switch (gameMode) {
11596           default:
11597             return;
11598           case IcsExamining:
11599             pauseExamForwardMostMove = forwardMostMove;
11600             pauseExamInvalid = FALSE;
11601             /* fall through */
11602           case IcsObserving:
11603           case IcsPlayingWhite:
11604           case IcsPlayingBlack:
11605             pausing = TRUE;
11606             ModeHighlight();
11607             return;
11608           case PlayFromGameFile:
11609             (void) StopLoadGameTimer();
11610             pausing = TRUE;
11611             ModeHighlight();
11612             break;
11613           case BeginningOfGame:
11614             if (appData.icsActive) return;
11615             /* else fall through */
11616           case MachinePlaysWhite:
11617           case MachinePlaysBlack:
11618           case TwoMachinesPlay:
11619             if (forwardMostMove == 0)
11620               return;           /* don't pause if no one has moved */
11621             if ((gameMode == MachinePlaysWhite &&
11622                  !WhiteOnMove(forwardMostMove)) ||
11623                 (gameMode == MachinePlaysBlack &&
11624                  WhiteOnMove(forwardMostMove))) {
11625                 StopClocks();
11626             }
11627             pausing = TRUE;
11628             ModeHighlight();
11629             break;
11630         }
11631     }
11632 }
11633
11634 void
11635 EditCommentEvent()
11636 {
11637     char title[MSG_SIZ];
11638
11639     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11640       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11641     } else {
11642       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11643                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11644                parseList[currentMove - 1]);
11645     }
11646
11647     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11648 }
11649
11650
11651 void
11652 EditTagsEvent()
11653 {
11654     char *tags = PGNTags(&gameInfo);
11655     EditTagsPopUp(tags, NULL);
11656     free(tags);
11657 }
11658
11659 void
11660 AnalyzeModeEvent()
11661 {
11662     if (appData.noChessProgram || gameMode == AnalyzeMode)
11663       return;
11664
11665     if (gameMode != AnalyzeFile) {
11666         if (!appData.icsEngineAnalyze) {
11667                EditGameEvent();
11668                if (gameMode != EditGame) return;
11669         }
11670         ResurrectChessProgram();
11671         SendToProgram("analyze\n", &first);
11672         first.analyzing = TRUE;
11673         /*first.maybeThinking = TRUE;*/
11674         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11675         EngineOutputPopUp();
11676     }
11677     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11678     pausing = FALSE;
11679     ModeHighlight();
11680     SetGameInfo();
11681
11682     StartAnalysisClock();
11683     GetTimeMark(&lastNodeCountTime);
11684     lastNodeCount = 0;
11685 }
11686
11687 void
11688 AnalyzeFileEvent()
11689 {
11690     if (appData.noChessProgram || gameMode == AnalyzeFile)
11691       return;
11692
11693     if (gameMode != AnalyzeMode) {
11694         EditGameEvent();
11695         if (gameMode != EditGame) return;
11696         ResurrectChessProgram();
11697         SendToProgram("analyze\n", &first);
11698         first.analyzing = TRUE;
11699         /*first.maybeThinking = TRUE;*/
11700         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11701         EngineOutputPopUp();
11702     }
11703     gameMode = AnalyzeFile;
11704     pausing = FALSE;
11705     ModeHighlight();
11706     SetGameInfo();
11707
11708     StartAnalysisClock();
11709     GetTimeMark(&lastNodeCountTime);
11710     lastNodeCount = 0;
11711 }
11712
11713 void
11714 MachineWhiteEvent()
11715 {
11716     char buf[MSG_SIZ];
11717     char *bookHit = NULL;
11718
11719     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11720       return;
11721
11722
11723     if (gameMode == PlayFromGameFile ||
11724         gameMode == TwoMachinesPlay  ||
11725         gameMode == Training         ||
11726         gameMode == AnalyzeMode      ||
11727         gameMode == EndOfGame)
11728         EditGameEvent();
11729
11730     if (gameMode == EditPosition)
11731         EditPositionDone(TRUE);
11732
11733     if (!WhiteOnMove(currentMove)) {
11734         DisplayError(_("It is not White's turn"), 0);
11735         return;
11736     }
11737
11738     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11739       ExitAnalyzeMode();
11740
11741     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11742         gameMode == AnalyzeFile)
11743         TruncateGame();
11744
11745     ResurrectChessProgram();    /* in case it isn't running */
11746     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11747         gameMode = MachinePlaysWhite;
11748         ResetClocks();
11749     } else
11750     gameMode = MachinePlaysWhite;
11751     pausing = FALSE;
11752     ModeHighlight();
11753     SetGameInfo();
11754     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11755     DisplayTitle(buf);
11756     if (first.sendName) {
11757       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11758       SendToProgram(buf, &first);
11759     }
11760     if (first.sendTime) {
11761       if (first.useColors) {
11762         SendToProgram("black\n", &first); /*gnu kludge*/
11763       }
11764       SendTimeRemaining(&first, TRUE);
11765     }
11766     if (first.useColors) {
11767       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11768     }
11769     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11770     SetMachineThinkingEnables();
11771     first.maybeThinking = TRUE;
11772     StartClocks();
11773     firstMove = FALSE;
11774
11775     if (appData.autoFlipView && !flipView) {
11776       flipView = !flipView;
11777       DrawPosition(FALSE, NULL);
11778       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11779     }
11780
11781     if(bookHit) { // [HGM] book: simulate book reply
11782         static char bookMove[MSG_SIZ]; // a bit generous?
11783
11784         programStats.nodes = programStats.depth = programStats.time =
11785         programStats.score = programStats.got_only_move = 0;
11786         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11787
11788         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11789         strcat(bookMove, bookHit);
11790         HandleMachineMove(bookMove, &first);
11791     }
11792 }
11793
11794 void
11795 MachineBlackEvent()
11796 {
11797   char buf[MSG_SIZ];
11798   char *bookHit = NULL;
11799
11800     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11801         return;
11802
11803
11804     if (gameMode == PlayFromGameFile ||
11805         gameMode == TwoMachinesPlay  ||
11806         gameMode == Training         ||
11807         gameMode == AnalyzeMode      ||
11808         gameMode == EndOfGame)
11809         EditGameEvent();
11810
11811     if (gameMode == EditPosition)
11812         EditPositionDone(TRUE);
11813
11814     if (WhiteOnMove(currentMove)) {
11815         DisplayError(_("It is not Black's turn"), 0);
11816         return;
11817     }
11818
11819     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11820       ExitAnalyzeMode();
11821
11822     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11823         gameMode == AnalyzeFile)
11824         TruncateGame();
11825
11826     ResurrectChessProgram();    /* in case it isn't running */
11827     gameMode = MachinePlaysBlack;
11828     pausing = FALSE;
11829     ModeHighlight();
11830     SetGameInfo();
11831     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11832     DisplayTitle(buf);
11833     if (first.sendName) {
11834       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11835       SendToProgram(buf, &first);
11836     }
11837     if (first.sendTime) {
11838       if (first.useColors) {
11839         SendToProgram("white\n", &first); /*gnu kludge*/
11840       }
11841       SendTimeRemaining(&first, FALSE);
11842     }
11843     if (first.useColors) {
11844       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11845     }
11846     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11847     SetMachineThinkingEnables();
11848     first.maybeThinking = TRUE;
11849     StartClocks();
11850
11851     if (appData.autoFlipView && flipView) {
11852       flipView = !flipView;
11853       DrawPosition(FALSE, NULL);
11854       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11855     }
11856     if(bookHit) { // [HGM] book: simulate book reply
11857         static char bookMove[MSG_SIZ]; // a bit generous?
11858
11859         programStats.nodes = programStats.depth = programStats.time =
11860         programStats.score = programStats.got_only_move = 0;
11861         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11862
11863         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11864         strcat(bookMove, bookHit);
11865         HandleMachineMove(bookMove, &first);
11866     }
11867 }
11868
11869
11870 void
11871 DisplayTwoMachinesTitle()
11872 {
11873     char buf[MSG_SIZ];
11874     if (appData.matchGames > 0) {
11875         if (first.twoMachinesColor[0] == 'w') {
11876           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11877                    gameInfo.white, gameInfo.black,
11878                    first.matchWins, second.matchWins,
11879                    matchGame - 1 - (first.matchWins + second.matchWins));
11880         } else {
11881           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11882                    gameInfo.white, gameInfo.black,
11883                    second.matchWins, first.matchWins,
11884                    matchGame - 1 - (first.matchWins + second.matchWins));
11885         }
11886     } else {
11887       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11888     }
11889     DisplayTitle(buf);
11890 }
11891
11892 void
11893 SettingsMenuIfReady()
11894 {
11895   if (second.lastPing != second.lastPong) {
11896     DisplayMessage("", _("Waiting for second chess program"));
11897     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11898     return;
11899   }
11900   ThawUI();
11901   DisplayMessage("", "");
11902   SettingsPopUp(&second);
11903 }
11904
11905 int
11906 WaitForSecond(DelayedEventCallback retry)
11907 {
11908     if (second.pr == NULL) {
11909         StartChessProgram(&second);
11910         if (second.protocolVersion == 1) {
11911           retry();
11912         } else {
11913           /* kludge: allow timeout for initial "feature" command */
11914           FreezeUI();
11915           DisplayMessage("", _("Starting second chess program"));
11916           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11917         }
11918         return 1;
11919     }
11920     return 0;
11921 }
11922
11923 void
11924 TwoMachinesEvent P((void))
11925 {
11926     int i;
11927     char buf[MSG_SIZ];
11928     ChessProgramState *onmove;
11929     char *bookHit = NULL;
11930
11931     if (appData.noChessProgram) return;
11932
11933     switch (gameMode) {
11934       case TwoMachinesPlay:
11935         return;
11936       case MachinePlaysWhite:
11937       case MachinePlaysBlack:
11938         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11939             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11940             return;
11941         }
11942         /* fall through */
11943       case BeginningOfGame:
11944       case PlayFromGameFile:
11945       case EndOfGame:
11946         EditGameEvent();
11947         if (gameMode != EditGame) return;
11948         break;
11949       case EditPosition:
11950         EditPositionDone(TRUE);
11951         break;
11952       case AnalyzeMode:
11953       case AnalyzeFile:
11954         ExitAnalyzeMode();
11955         break;
11956       case EditGame:
11957       default:
11958         break;
11959     }
11960
11961 //    forwardMostMove = currentMove;
11962     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11963     ResurrectChessProgram();    /* in case first program isn't running */
11964
11965     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11966     DisplayMessage("", "");
11967     InitChessProgram(&second, FALSE);
11968     SendToProgram("force\n", &second);
11969     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11970       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11971       return;
11972     }
11973     if (startedFromSetupPosition) {
11974         SendBoard(&second, backwardMostMove);
11975     if (appData.debugMode) {
11976         fprintf(debugFP, "Two Machines\n");
11977     }
11978     }
11979     for (i = backwardMostMove; i < forwardMostMove; i++) {
11980         SendMoveToProgram(i, &second);
11981     }
11982
11983     gameMode = TwoMachinesPlay;
11984     pausing = FALSE;
11985     ModeHighlight();
11986     SetGameInfo();
11987     DisplayTwoMachinesTitle();
11988     firstMove = TRUE;
11989     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11990         onmove = &first;
11991     } else {
11992         onmove = &second;
11993     }
11994
11995     SendToProgram(first.computerString, &first);
11996     if (first.sendName) {
11997       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11998       SendToProgram(buf, &first);
11999     }
12000     SendToProgram(second.computerString, &second);
12001     if (second.sendName) {
12002       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12003       SendToProgram(buf, &second);
12004     }
12005
12006     ResetClocks();
12007     if (!first.sendTime || !second.sendTime) {
12008         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12009         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12010     }
12011     if (onmove->sendTime) {
12012       if (onmove->useColors) {
12013         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12014       }
12015       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12016     }
12017     if (onmove->useColors) {
12018       SendToProgram(onmove->twoMachinesColor, onmove);
12019     }
12020     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12021 //    SendToProgram("go\n", onmove);
12022     onmove->maybeThinking = TRUE;
12023     SetMachineThinkingEnables();
12024
12025     StartClocks();
12026
12027     if(bookHit) { // [HGM] book: simulate book reply
12028         static char bookMove[MSG_SIZ]; // a bit generous?
12029
12030         programStats.nodes = programStats.depth = programStats.time =
12031         programStats.score = programStats.got_only_move = 0;
12032         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12033
12034         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12035         strcat(bookMove, bookHit);
12036         savedMessage = bookMove; // args for deferred call
12037         savedState = onmove;
12038         ScheduleDelayedEvent(DeferredBookMove, 1);
12039     }
12040 }
12041
12042 void
12043 TrainingEvent()
12044 {
12045     if (gameMode == Training) {
12046       SetTrainingModeOff();
12047       gameMode = PlayFromGameFile;
12048       DisplayMessage("", _("Training mode off"));
12049     } else {
12050       gameMode = Training;
12051       animateTraining = appData.animate;
12052
12053       /* make sure we are not already at the end of the game */
12054       if (currentMove < forwardMostMove) {
12055         SetTrainingModeOn();
12056         DisplayMessage("", _("Training mode on"));
12057       } else {
12058         gameMode = PlayFromGameFile;
12059         DisplayError(_("Already at end of game"), 0);
12060       }
12061     }
12062     ModeHighlight();
12063 }
12064
12065 void
12066 IcsClientEvent()
12067 {
12068     if (!appData.icsActive) return;
12069     switch (gameMode) {
12070       case IcsPlayingWhite:
12071       case IcsPlayingBlack:
12072       case IcsObserving:
12073       case IcsIdle:
12074       case BeginningOfGame:
12075       case IcsExamining:
12076         return;
12077
12078       case EditGame:
12079         break;
12080
12081       case EditPosition:
12082         EditPositionDone(TRUE);
12083         break;
12084
12085       case AnalyzeMode:
12086       case AnalyzeFile:
12087         ExitAnalyzeMode();
12088         break;
12089
12090       default:
12091         EditGameEvent();
12092         break;
12093     }
12094
12095     gameMode = IcsIdle;
12096     ModeHighlight();
12097     return;
12098 }
12099
12100
12101 void
12102 EditGameEvent()
12103 {
12104     int i;
12105
12106     switch (gameMode) {
12107       case Training:
12108         SetTrainingModeOff();
12109         break;
12110       case MachinePlaysWhite:
12111       case MachinePlaysBlack:
12112       case BeginningOfGame:
12113         SendToProgram("force\n", &first);
12114         SetUserThinkingEnables();
12115         break;
12116       case PlayFromGameFile:
12117         (void) StopLoadGameTimer();
12118         if (gameFileFP != NULL) {
12119             gameFileFP = NULL;
12120         }
12121         break;
12122       case EditPosition:
12123         EditPositionDone(TRUE);
12124         break;
12125       case AnalyzeMode:
12126       case AnalyzeFile:
12127         ExitAnalyzeMode();
12128         SendToProgram("force\n", &first);
12129         break;
12130       case TwoMachinesPlay:
12131         GameEnds(EndOfFile, NULL, GE_PLAYER);
12132         ResurrectChessProgram();
12133         SetUserThinkingEnables();
12134         break;
12135       case EndOfGame:
12136         ResurrectChessProgram();
12137         break;
12138       case IcsPlayingBlack:
12139       case IcsPlayingWhite:
12140         DisplayError(_("Warning: You are still playing a game"), 0);
12141         break;
12142       case IcsObserving:
12143         DisplayError(_("Warning: You are still observing a game"), 0);
12144         break;
12145       case IcsExamining:
12146         DisplayError(_("Warning: You are still examining a game"), 0);
12147         break;
12148       case IcsIdle:
12149         break;
12150       case EditGame:
12151       default:
12152         return;
12153     }
12154
12155     pausing = FALSE;
12156     StopClocks();
12157     first.offeredDraw = second.offeredDraw = 0;
12158
12159     if (gameMode == PlayFromGameFile) {
12160         whiteTimeRemaining = timeRemaining[0][currentMove];
12161         blackTimeRemaining = timeRemaining[1][currentMove];
12162         DisplayTitle("");
12163     }
12164
12165     if (gameMode == MachinePlaysWhite ||
12166         gameMode == MachinePlaysBlack ||
12167         gameMode == TwoMachinesPlay ||
12168         gameMode == EndOfGame) {
12169         i = forwardMostMove;
12170         while (i > currentMove) {
12171             SendToProgram("undo\n", &first);
12172             i--;
12173         }
12174         whiteTimeRemaining = timeRemaining[0][currentMove];
12175         blackTimeRemaining = timeRemaining[1][currentMove];
12176         DisplayBothClocks();
12177         if (whiteFlag || blackFlag) {
12178             whiteFlag = blackFlag = 0;
12179         }
12180         DisplayTitle("");
12181     }
12182
12183     gameMode = EditGame;
12184     ModeHighlight();
12185     SetGameInfo();
12186 }
12187
12188
12189 void
12190 EditPositionEvent()
12191 {
12192     if (gameMode == EditPosition) {
12193         EditGameEvent();
12194         return;
12195     }
12196
12197     EditGameEvent();
12198     if (gameMode != EditGame) return;
12199
12200     gameMode = EditPosition;
12201     ModeHighlight();
12202     SetGameInfo();
12203     if (currentMove > 0)
12204       CopyBoard(boards[0], boards[currentMove]);
12205
12206     blackPlaysFirst = !WhiteOnMove(currentMove);
12207     ResetClocks();
12208     currentMove = forwardMostMove = backwardMostMove = 0;
12209     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12210     DisplayMove(-1);
12211 }
12212
12213 void
12214 ExitAnalyzeMode()
12215 {
12216     /* [DM] icsEngineAnalyze - possible call from other functions */
12217     if (appData.icsEngineAnalyze) {
12218         appData.icsEngineAnalyze = FALSE;
12219
12220         DisplayMessage("",_("Close ICS engine analyze..."));
12221     }
12222     if (first.analysisSupport && first.analyzing) {
12223       SendToProgram("exit\n", &first);
12224       first.analyzing = FALSE;
12225     }
12226     thinkOutput[0] = NULLCHAR;
12227 }
12228
12229 void
12230 EditPositionDone(Boolean fakeRights)
12231 {
12232     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12233
12234     startedFromSetupPosition = TRUE;
12235     InitChessProgram(&first, FALSE);
12236     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12237       boards[0][EP_STATUS] = EP_NONE;
12238       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12239     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12240         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12241         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12242       } else boards[0][CASTLING][2] = NoRights;
12243     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12244         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12245         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12246       } else boards[0][CASTLING][5] = NoRights;
12247     }
12248     SendToProgram("force\n", &first);
12249     if (blackPlaysFirst) {
12250         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12251         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12252         currentMove = forwardMostMove = backwardMostMove = 1;
12253         CopyBoard(boards[1], boards[0]);
12254     } else {
12255         currentMove = forwardMostMove = backwardMostMove = 0;
12256     }
12257     SendBoard(&first, forwardMostMove);
12258     if (appData.debugMode) {
12259         fprintf(debugFP, "EditPosDone\n");
12260     }
12261     DisplayTitle("");
12262     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12263     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12264     gameMode = EditGame;
12265     ModeHighlight();
12266     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12267     ClearHighlights(); /* [AS] */
12268 }
12269
12270 /* Pause for `ms' milliseconds */
12271 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12272 void
12273 TimeDelay(ms)
12274      long ms;
12275 {
12276     TimeMark m1, m2;
12277
12278     GetTimeMark(&m1);
12279     do {
12280         GetTimeMark(&m2);
12281     } while (SubtractTimeMarks(&m2, &m1) < ms);
12282 }
12283
12284 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12285 void
12286 SendMultiLineToICS(buf)
12287      char *buf;
12288 {
12289     char temp[MSG_SIZ+1], *p;
12290     int len;
12291
12292     len = strlen(buf);
12293     if (len > MSG_SIZ)
12294       len = MSG_SIZ;
12295
12296     strncpy(temp, buf, len);
12297     temp[len] = 0;
12298
12299     p = temp;
12300     while (*p) {
12301         if (*p == '\n' || *p == '\r')
12302           *p = ' ';
12303         ++p;
12304     }
12305
12306     strcat(temp, "\n");
12307     SendToICS(temp);
12308     SendToPlayer(temp, strlen(temp));
12309 }
12310
12311 void
12312 SetWhiteToPlayEvent()
12313 {
12314     if (gameMode == EditPosition) {
12315         blackPlaysFirst = FALSE;
12316         DisplayBothClocks();    /* works because currentMove is 0 */
12317     } else if (gameMode == IcsExamining) {
12318         SendToICS(ics_prefix);
12319         SendToICS("tomove white\n");
12320     }
12321 }
12322
12323 void
12324 SetBlackToPlayEvent()
12325 {
12326     if (gameMode == EditPosition) {
12327         blackPlaysFirst = TRUE;
12328         currentMove = 1;        /* kludge */
12329         DisplayBothClocks();
12330         currentMove = 0;
12331     } else if (gameMode == IcsExamining) {
12332         SendToICS(ics_prefix);
12333         SendToICS("tomove black\n");
12334     }
12335 }
12336
12337 void
12338 EditPositionMenuEvent(selection, x, y)
12339      ChessSquare selection;
12340      int x, y;
12341 {
12342     char buf[MSG_SIZ];
12343     ChessSquare piece = boards[0][y][x];
12344
12345     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12346
12347     switch (selection) {
12348       case ClearBoard:
12349         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12350             SendToICS(ics_prefix);
12351             SendToICS("bsetup clear\n");
12352         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12353             SendToICS(ics_prefix);
12354             SendToICS("clearboard\n");
12355         } else {
12356             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12357                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12358                 for (y = 0; y < BOARD_HEIGHT; y++) {
12359                     if (gameMode == IcsExamining) {
12360                         if (boards[currentMove][y][x] != EmptySquare) {
12361                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12362                                     AAA + x, ONE + y);
12363                             SendToICS(buf);
12364                         }
12365                     } else {
12366                         boards[0][y][x] = p;
12367                     }
12368                 }
12369             }
12370         }
12371         if (gameMode == EditPosition) {
12372             DrawPosition(FALSE, boards[0]);
12373         }
12374         break;
12375
12376       case WhitePlay:
12377         SetWhiteToPlayEvent();
12378         break;
12379
12380       case BlackPlay:
12381         SetBlackToPlayEvent();
12382         break;
12383
12384       case EmptySquare:
12385         if (gameMode == IcsExamining) {
12386             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12387             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12388             SendToICS(buf);
12389         } else {
12390             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12391                 if(x == BOARD_LEFT-2) {
12392                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12393                     boards[0][y][1] = 0;
12394                 } else
12395                 if(x == BOARD_RGHT+1) {
12396                     if(y >= gameInfo.holdingsSize) break;
12397                     boards[0][y][BOARD_WIDTH-2] = 0;
12398                 } else break;
12399             }
12400             boards[0][y][x] = EmptySquare;
12401             DrawPosition(FALSE, boards[0]);
12402         }
12403         break;
12404
12405       case PromotePiece:
12406         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12407            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12408             selection = (ChessSquare) (PROMOTED piece);
12409         } else if(piece == EmptySquare) selection = WhiteSilver;
12410         else selection = (ChessSquare)((int)piece - 1);
12411         goto defaultlabel;
12412
12413       case DemotePiece:
12414         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12415            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12416             selection = (ChessSquare) (DEMOTED piece);
12417         } else if(piece == EmptySquare) selection = BlackSilver;
12418         else selection = (ChessSquare)((int)piece + 1);
12419         goto defaultlabel;
12420
12421       case WhiteQueen:
12422       case BlackQueen:
12423         if(gameInfo.variant == VariantShatranj ||
12424            gameInfo.variant == VariantXiangqi  ||
12425            gameInfo.variant == VariantCourier  ||
12426            gameInfo.variant == VariantMakruk     )
12427             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12428         goto defaultlabel;
12429
12430       case WhiteKing:
12431       case BlackKing:
12432         if(gameInfo.variant == VariantXiangqi)
12433             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12434         if(gameInfo.variant == VariantKnightmate)
12435             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12436       default:
12437         defaultlabel:
12438         if (gameMode == IcsExamining) {
12439             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12440             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12441                      PieceToChar(selection), AAA + x, ONE + y);
12442             SendToICS(buf);
12443         } else {
12444             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12445                 int n;
12446                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12447                     n = PieceToNumber(selection - BlackPawn);
12448                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12449                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12450                     boards[0][BOARD_HEIGHT-1-n][1]++;
12451                 } else
12452                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12453                     n = PieceToNumber(selection);
12454                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12455                     boards[0][n][BOARD_WIDTH-1] = selection;
12456                     boards[0][n][BOARD_WIDTH-2]++;
12457                 }
12458             } else
12459             boards[0][y][x] = selection;
12460             DrawPosition(TRUE, boards[0]);
12461         }
12462         break;
12463     }
12464 }
12465
12466
12467 void
12468 DropMenuEvent(selection, x, y)
12469      ChessSquare selection;
12470      int x, y;
12471 {
12472     ChessMove moveType;
12473
12474     switch (gameMode) {
12475       case IcsPlayingWhite:
12476       case MachinePlaysBlack:
12477         if (!WhiteOnMove(currentMove)) {
12478             DisplayMoveError(_("It is Black's turn"));
12479             return;
12480         }
12481         moveType = WhiteDrop;
12482         break;
12483       case IcsPlayingBlack:
12484       case MachinePlaysWhite:
12485         if (WhiteOnMove(currentMove)) {
12486             DisplayMoveError(_("It is White's turn"));
12487             return;
12488         }
12489         moveType = BlackDrop;
12490         break;
12491       case EditGame:
12492         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12493         break;
12494       default:
12495         return;
12496     }
12497
12498     if (moveType == BlackDrop && selection < BlackPawn) {
12499       selection = (ChessSquare) ((int) selection
12500                                  + (int) BlackPawn - (int) WhitePawn);
12501     }
12502     if (boards[currentMove][y][x] != EmptySquare) {
12503         DisplayMoveError(_("That square is occupied"));
12504         return;
12505     }
12506
12507     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12508 }
12509
12510 void
12511 AcceptEvent()
12512 {
12513     /* Accept a pending offer of any kind from opponent */
12514
12515     if (appData.icsActive) {
12516         SendToICS(ics_prefix);
12517         SendToICS("accept\n");
12518     } else if (cmailMsgLoaded) {
12519         if (currentMove == cmailOldMove &&
12520             commentList[cmailOldMove] != NULL &&
12521             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12522                    "Black offers a draw" : "White offers a draw")) {
12523             TruncateGame();
12524             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12525             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12526         } else {
12527             DisplayError(_("There is no pending offer on this move"), 0);
12528             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12529         }
12530     } else {
12531         /* Not used for offers from chess program */
12532     }
12533 }
12534
12535 void
12536 DeclineEvent()
12537 {
12538     /* Decline a pending offer of any kind from opponent */
12539
12540     if (appData.icsActive) {
12541         SendToICS(ics_prefix);
12542         SendToICS("decline\n");
12543     } else if (cmailMsgLoaded) {
12544         if (currentMove == cmailOldMove &&
12545             commentList[cmailOldMove] != NULL &&
12546             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12547                    "Black offers a draw" : "White offers a draw")) {
12548 #ifdef NOTDEF
12549             AppendComment(cmailOldMove, "Draw declined", TRUE);
12550             DisplayComment(cmailOldMove - 1, "Draw declined");
12551 #endif /*NOTDEF*/
12552         } else {
12553             DisplayError(_("There is no pending offer on this move"), 0);
12554         }
12555     } else {
12556         /* Not used for offers from chess program */
12557     }
12558 }
12559
12560 void
12561 RematchEvent()
12562 {
12563     /* Issue ICS rematch command */
12564     if (appData.icsActive) {
12565         SendToICS(ics_prefix);
12566         SendToICS("rematch\n");
12567     }
12568 }
12569
12570 void
12571 CallFlagEvent()
12572 {
12573     /* Call your opponent's flag (claim a win on time) */
12574     if (appData.icsActive) {
12575         SendToICS(ics_prefix);
12576         SendToICS("flag\n");
12577     } else {
12578         switch (gameMode) {
12579           default:
12580             return;
12581           case MachinePlaysWhite:
12582             if (whiteFlag) {
12583                 if (blackFlag)
12584                   GameEnds(GameIsDrawn, "Both players ran out of time",
12585                            GE_PLAYER);
12586                 else
12587                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12588             } else {
12589                 DisplayError(_("Your opponent is not out of time"), 0);
12590             }
12591             break;
12592           case MachinePlaysBlack:
12593             if (blackFlag) {
12594                 if (whiteFlag)
12595                   GameEnds(GameIsDrawn, "Both players ran out of time",
12596                            GE_PLAYER);
12597                 else
12598                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12599             } else {
12600                 DisplayError(_("Your opponent is not out of time"), 0);
12601             }
12602             break;
12603         }
12604     }
12605 }
12606
12607 void
12608 ClockClick(int which)
12609 {       // [HGM] code moved to back-end from winboard.c
12610         if(which) { // black clock
12611           if (gameMode == EditPosition || gameMode == IcsExamining) {
12612             SetBlackToPlayEvent();
12613           } else if (gameMode == EditGame || shiftKey) {
12614             AdjustClock(which, -1);
12615           } else if (gameMode == IcsPlayingWhite ||
12616                      gameMode == MachinePlaysBlack) {
12617             CallFlagEvent();
12618           }
12619         } else { // white clock
12620           if (gameMode == EditPosition || gameMode == IcsExamining) {
12621             SetWhiteToPlayEvent();
12622           } else if (gameMode == EditGame || shiftKey) {
12623             AdjustClock(which, -1);
12624           } else if (gameMode == IcsPlayingBlack ||
12625                    gameMode == MachinePlaysWhite) {
12626             CallFlagEvent();
12627           }
12628         }
12629 }
12630
12631 void
12632 DrawEvent()
12633 {
12634     /* Offer draw or accept pending draw offer from opponent */
12635
12636     if (appData.icsActive) {
12637         /* Note: tournament rules require draw offers to be
12638            made after you make your move but before you punch
12639            your clock.  Currently ICS doesn't let you do that;
12640            instead, you immediately punch your clock after making
12641            a move, but you can offer a draw at any time. */
12642
12643         SendToICS(ics_prefix);
12644         SendToICS("draw\n");
12645         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12646     } else if (cmailMsgLoaded) {
12647         if (currentMove == cmailOldMove &&
12648             commentList[cmailOldMove] != NULL &&
12649             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12650                    "Black offers a draw" : "White offers a draw")) {
12651             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12652             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12653         } else if (currentMove == cmailOldMove + 1) {
12654             char *offer = WhiteOnMove(cmailOldMove) ?
12655               "White offers a draw" : "Black offers a draw";
12656             AppendComment(currentMove, offer, TRUE);
12657             DisplayComment(currentMove - 1, offer);
12658             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12659         } else {
12660             DisplayError(_("You must make your move before offering a draw"), 0);
12661             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12662         }
12663     } else if (first.offeredDraw) {
12664         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12665     } else {
12666         if (first.sendDrawOffers) {
12667             SendToProgram("draw\n", &first);
12668             userOfferedDraw = TRUE;
12669         }
12670     }
12671 }
12672
12673 void
12674 AdjournEvent()
12675 {
12676     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12677
12678     if (appData.icsActive) {
12679         SendToICS(ics_prefix);
12680         SendToICS("adjourn\n");
12681     } else {
12682         /* Currently GNU Chess doesn't offer or accept Adjourns */
12683     }
12684 }
12685
12686
12687 void
12688 AbortEvent()
12689 {
12690     /* Offer Abort or accept pending Abort offer from opponent */
12691
12692     if (appData.icsActive) {
12693         SendToICS(ics_prefix);
12694         SendToICS("abort\n");
12695     } else {
12696         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12697     }
12698 }
12699
12700 void
12701 ResignEvent()
12702 {
12703     /* Resign.  You can do this even if it's not your turn. */
12704
12705     if (appData.icsActive) {
12706         SendToICS(ics_prefix);
12707         SendToICS("resign\n");
12708     } else {
12709         switch (gameMode) {
12710           case MachinePlaysWhite:
12711             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12712             break;
12713           case MachinePlaysBlack:
12714             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12715             break;
12716           case EditGame:
12717             if (cmailMsgLoaded) {
12718                 TruncateGame();
12719                 if (WhiteOnMove(cmailOldMove)) {
12720                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12721                 } else {
12722                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12723                 }
12724                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12725             }
12726             break;
12727           default:
12728             break;
12729         }
12730     }
12731 }
12732
12733
12734 void
12735 StopObservingEvent()
12736 {
12737     /* Stop observing current games */
12738     SendToICS(ics_prefix);
12739     SendToICS("unobserve\n");
12740 }
12741
12742 void
12743 StopExaminingEvent()
12744 {
12745     /* Stop observing current game */
12746     SendToICS(ics_prefix);
12747     SendToICS("unexamine\n");
12748 }
12749
12750 void
12751 ForwardInner(target)
12752      int target;
12753 {
12754     int limit;
12755
12756     if (appData.debugMode)
12757         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12758                 target, currentMove, forwardMostMove);
12759
12760     if (gameMode == EditPosition)
12761       return;
12762
12763     if (gameMode == PlayFromGameFile && !pausing)
12764       PauseEvent();
12765
12766     if (gameMode == IcsExamining && pausing)
12767       limit = pauseExamForwardMostMove;
12768     else
12769       limit = forwardMostMove;
12770
12771     if (target > limit) target = limit;
12772
12773     if (target > 0 && moveList[target - 1][0]) {
12774         int fromX, fromY, toX, toY;
12775         toX = moveList[target - 1][2] - AAA;
12776         toY = moveList[target - 1][3] - ONE;
12777         if (moveList[target - 1][1] == '@') {
12778             if (appData.highlightLastMove) {
12779                 SetHighlights(-1, -1, toX, toY);
12780             }
12781         } else {
12782             fromX = moveList[target - 1][0] - AAA;
12783             fromY = moveList[target - 1][1] - ONE;
12784             if (target == currentMove + 1) {
12785                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12786             }
12787             if (appData.highlightLastMove) {
12788                 SetHighlights(fromX, fromY, toX, toY);
12789             }
12790         }
12791     }
12792     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12793         gameMode == Training || gameMode == PlayFromGameFile ||
12794         gameMode == AnalyzeFile) {
12795         while (currentMove < target) {
12796             SendMoveToProgram(currentMove++, &first);
12797         }
12798     } else {
12799         currentMove = target;
12800     }
12801
12802     if (gameMode == EditGame || gameMode == EndOfGame) {
12803         whiteTimeRemaining = timeRemaining[0][currentMove];
12804         blackTimeRemaining = timeRemaining[1][currentMove];
12805     }
12806     DisplayBothClocks();
12807     DisplayMove(currentMove - 1);
12808     DrawPosition(FALSE, boards[currentMove]);
12809     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12810     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12811         DisplayComment(currentMove - 1, commentList[currentMove]);
12812     }
12813 }
12814
12815
12816 void
12817 ForwardEvent()
12818 {
12819     if (gameMode == IcsExamining && !pausing) {
12820         SendToICS(ics_prefix);
12821         SendToICS("forward\n");
12822     } else {
12823         ForwardInner(currentMove + 1);
12824     }
12825 }
12826
12827 void
12828 ToEndEvent()
12829 {
12830     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12831         /* to optimze, we temporarily turn off analysis mode while we feed
12832          * the remaining moves to the engine. Otherwise we get analysis output
12833          * after each move.
12834          */
12835         if (first.analysisSupport) {
12836           SendToProgram("exit\nforce\n", &first);
12837           first.analyzing = FALSE;
12838         }
12839     }
12840
12841     if (gameMode == IcsExamining && !pausing) {
12842         SendToICS(ics_prefix);
12843         SendToICS("forward 999999\n");
12844     } else {
12845         ForwardInner(forwardMostMove);
12846     }
12847
12848     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12849         /* we have fed all the moves, so reactivate analysis mode */
12850         SendToProgram("analyze\n", &first);
12851         first.analyzing = TRUE;
12852         /*first.maybeThinking = TRUE;*/
12853         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12854     }
12855 }
12856
12857 void
12858 BackwardInner(target)
12859      int target;
12860 {
12861     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12862
12863     if (appData.debugMode)
12864         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12865                 target, currentMove, forwardMostMove);
12866
12867     if (gameMode == EditPosition) return;
12868     if (currentMove <= backwardMostMove) {
12869         ClearHighlights();
12870         DrawPosition(full_redraw, boards[currentMove]);
12871         return;
12872     }
12873     if (gameMode == PlayFromGameFile && !pausing)
12874       PauseEvent();
12875
12876     if (moveList[target][0]) {
12877         int fromX, fromY, toX, toY;
12878         toX = moveList[target][2] - AAA;
12879         toY = moveList[target][3] - ONE;
12880         if (moveList[target][1] == '@') {
12881             if (appData.highlightLastMove) {
12882                 SetHighlights(-1, -1, toX, toY);
12883             }
12884         } else {
12885             fromX = moveList[target][0] - AAA;
12886             fromY = moveList[target][1] - ONE;
12887             if (target == currentMove - 1) {
12888                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12889             }
12890             if (appData.highlightLastMove) {
12891                 SetHighlights(fromX, fromY, toX, toY);
12892             }
12893         }
12894     }
12895     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12896         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12897         while (currentMove > target) {
12898             SendToProgram("undo\n", &first);
12899             currentMove--;
12900         }
12901     } else {
12902         currentMove = target;
12903     }
12904
12905     if (gameMode == EditGame || gameMode == EndOfGame) {
12906         whiteTimeRemaining = timeRemaining[0][currentMove];
12907         blackTimeRemaining = timeRemaining[1][currentMove];
12908     }
12909     DisplayBothClocks();
12910     DisplayMove(currentMove - 1);
12911     DrawPosition(full_redraw, boards[currentMove]);
12912     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12913     // [HGM] PV info: routine tests if comment empty
12914     DisplayComment(currentMove - 1, commentList[currentMove]);
12915 }
12916
12917 void
12918 BackwardEvent()
12919 {
12920     if (gameMode == IcsExamining && !pausing) {
12921         SendToICS(ics_prefix);
12922         SendToICS("backward\n");
12923     } else {
12924         BackwardInner(currentMove - 1);
12925     }
12926 }
12927
12928 void
12929 ToStartEvent()
12930 {
12931     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12932         /* to optimize, we temporarily turn off analysis mode while we undo
12933          * all the moves. Otherwise we get analysis output after each undo.
12934          */
12935         if (first.analysisSupport) {
12936           SendToProgram("exit\nforce\n", &first);
12937           first.analyzing = FALSE;
12938         }
12939     }
12940
12941     if (gameMode == IcsExamining && !pausing) {
12942         SendToICS(ics_prefix);
12943         SendToICS("backward 999999\n");
12944     } else {
12945         BackwardInner(backwardMostMove);
12946     }
12947
12948     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12949         /* we have fed all the moves, so reactivate analysis mode */
12950         SendToProgram("analyze\n", &first);
12951         first.analyzing = TRUE;
12952         /*first.maybeThinking = TRUE;*/
12953         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12954     }
12955 }
12956
12957 void
12958 ToNrEvent(int to)
12959 {
12960   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12961   if (to >= forwardMostMove) to = forwardMostMove;
12962   if (to <= backwardMostMove) to = backwardMostMove;
12963   if (to < currentMove) {
12964     BackwardInner(to);
12965   } else {
12966     ForwardInner(to);
12967   }
12968 }
12969
12970 void
12971 RevertEvent(Boolean annotate)
12972 {
12973     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12974         return;
12975     }
12976     if (gameMode != IcsExamining) {
12977         DisplayError(_("You are not examining a game"), 0);
12978         return;
12979     }
12980     if (pausing) {
12981         DisplayError(_("You can't revert while pausing"), 0);
12982         return;
12983     }
12984     SendToICS(ics_prefix);
12985     SendToICS("revert\n");
12986 }
12987
12988 void
12989 RetractMoveEvent()
12990 {
12991     switch (gameMode) {
12992       case MachinePlaysWhite:
12993       case MachinePlaysBlack:
12994         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12995             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12996             return;
12997         }
12998         if (forwardMostMove < 2) return;
12999         currentMove = forwardMostMove = forwardMostMove - 2;
13000         whiteTimeRemaining = timeRemaining[0][currentMove];
13001         blackTimeRemaining = timeRemaining[1][currentMove];
13002         DisplayBothClocks();
13003         DisplayMove(currentMove - 1);
13004         ClearHighlights();/*!! could figure this out*/
13005         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13006         SendToProgram("remove\n", &first);
13007         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13008         break;
13009
13010       case BeginningOfGame:
13011       default:
13012         break;
13013
13014       case IcsPlayingWhite:
13015       case IcsPlayingBlack:
13016         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13017             SendToICS(ics_prefix);
13018             SendToICS("takeback 2\n");
13019         } else {
13020             SendToICS(ics_prefix);
13021             SendToICS("takeback 1\n");
13022         }
13023         break;
13024     }
13025 }
13026
13027 void
13028 MoveNowEvent()
13029 {
13030     ChessProgramState *cps;
13031
13032     switch (gameMode) {
13033       case MachinePlaysWhite:
13034         if (!WhiteOnMove(forwardMostMove)) {
13035             DisplayError(_("It is your turn"), 0);
13036             return;
13037         }
13038         cps = &first;
13039         break;
13040       case MachinePlaysBlack:
13041         if (WhiteOnMove(forwardMostMove)) {
13042             DisplayError(_("It is your turn"), 0);
13043             return;
13044         }
13045         cps = &first;
13046         break;
13047       case TwoMachinesPlay:
13048         if (WhiteOnMove(forwardMostMove) ==
13049             (first.twoMachinesColor[0] == 'w')) {
13050             cps = &first;
13051         } else {
13052             cps = &second;
13053         }
13054         break;
13055       case BeginningOfGame:
13056       default:
13057         return;
13058     }
13059     SendToProgram("?\n", cps);
13060 }
13061
13062 void
13063 TruncateGameEvent()
13064 {
13065     EditGameEvent();
13066     if (gameMode != EditGame) return;
13067     TruncateGame();
13068 }
13069
13070 void
13071 TruncateGame()
13072 {
13073     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13074     if (forwardMostMove > currentMove) {
13075         if (gameInfo.resultDetails != NULL) {
13076             free(gameInfo.resultDetails);
13077             gameInfo.resultDetails = NULL;
13078             gameInfo.result = GameUnfinished;
13079         }
13080         forwardMostMove = currentMove;
13081         HistorySet(parseList, backwardMostMove, forwardMostMove,
13082                    currentMove-1);
13083     }
13084 }
13085
13086 void
13087 HintEvent()
13088 {
13089     if (appData.noChessProgram) return;
13090     switch (gameMode) {
13091       case MachinePlaysWhite:
13092         if (WhiteOnMove(forwardMostMove)) {
13093             DisplayError(_("Wait until your turn"), 0);
13094             return;
13095         }
13096         break;
13097       case BeginningOfGame:
13098       case MachinePlaysBlack:
13099         if (!WhiteOnMove(forwardMostMove)) {
13100             DisplayError(_("Wait until your turn"), 0);
13101             return;
13102         }
13103         break;
13104       default:
13105         DisplayError(_("No hint available"), 0);
13106         return;
13107     }
13108     SendToProgram("hint\n", &first);
13109     hintRequested = TRUE;
13110 }
13111
13112 void
13113 BookEvent()
13114 {
13115     if (appData.noChessProgram) return;
13116     switch (gameMode) {
13117       case MachinePlaysWhite:
13118         if (WhiteOnMove(forwardMostMove)) {
13119             DisplayError(_("Wait until your turn"), 0);
13120             return;
13121         }
13122         break;
13123       case BeginningOfGame:
13124       case MachinePlaysBlack:
13125         if (!WhiteOnMove(forwardMostMove)) {
13126             DisplayError(_("Wait until your turn"), 0);
13127             return;
13128         }
13129         break;
13130       case EditPosition:
13131         EditPositionDone(TRUE);
13132         break;
13133       case TwoMachinesPlay:
13134         return;
13135       default:
13136         break;
13137     }
13138     SendToProgram("bk\n", &first);
13139     bookOutput[0] = NULLCHAR;
13140     bookRequested = TRUE;
13141 }
13142
13143 void
13144 AboutGameEvent()
13145 {
13146     char *tags = PGNTags(&gameInfo);
13147     TagsPopUp(tags, CmailMsg());
13148     free(tags);
13149 }
13150
13151 /* end button procedures */
13152
13153 void
13154 PrintPosition(fp, move)
13155      FILE *fp;
13156      int move;
13157 {
13158     int i, j;
13159
13160     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13161         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13162             char c = PieceToChar(boards[move][i][j]);
13163             fputc(c == 'x' ? '.' : c, fp);
13164             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13165         }
13166     }
13167     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13168       fprintf(fp, "white to play\n");
13169     else
13170       fprintf(fp, "black to play\n");
13171 }
13172
13173 void
13174 PrintOpponents(fp)
13175      FILE *fp;
13176 {
13177     if (gameInfo.white != NULL) {
13178         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13179     } else {
13180         fprintf(fp, "\n");
13181     }
13182 }
13183
13184 /* Find last component of program's own name, using some heuristics */
13185 void
13186 TidyProgramName(prog, host, buf)
13187      char *prog, *host, buf[MSG_SIZ];
13188 {
13189     char *p, *q;
13190     int local = (strcmp(host, "localhost") == 0);
13191     while (!local && (p = strchr(prog, ';')) != NULL) {
13192         p++;
13193         while (*p == ' ') p++;
13194         prog = p;
13195     }
13196     if (*prog == '"' || *prog == '\'') {
13197         q = strchr(prog + 1, *prog);
13198     } else {
13199         q = strchr(prog, ' ');
13200     }
13201     if (q == NULL) q = prog + strlen(prog);
13202     p = q;
13203     while (p >= prog && *p != '/' && *p != '\\') p--;
13204     p++;
13205     if(p == prog && *p == '"') p++;
13206     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13207     memcpy(buf, p, q - p);
13208     buf[q - p] = NULLCHAR;
13209     if (!local) {
13210         strcat(buf, "@");
13211         strcat(buf, host);
13212     }
13213 }
13214
13215 char *
13216 TimeControlTagValue()
13217 {
13218     char buf[MSG_SIZ];
13219     if (!appData.clockMode) {
13220       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13221     } else if (movesPerSession > 0) {
13222       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13223     } else if (timeIncrement == 0) {
13224       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13225     } else {
13226       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13227     }
13228     return StrSave(buf);
13229 }
13230
13231 void
13232 SetGameInfo()
13233 {
13234     /* This routine is used only for certain modes */
13235     VariantClass v = gameInfo.variant;
13236     ChessMove r = GameUnfinished;
13237     char *p = NULL;
13238
13239     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13240         r = gameInfo.result;
13241         p = gameInfo.resultDetails;
13242         gameInfo.resultDetails = NULL;
13243     }
13244     ClearGameInfo(&gameInfo);
13245     gameInfo.variant = v;
13246
13247     switch (gameMode) {
13248       case MachinePlaysWhite:
13249         gameInfo.event = StrSave( appData.pgnEventHeader );
13250         gameInfo.site = StrSave(HostName());
13251         gameInfo.date = PGNDate();
13252         gameInfo.round = StrSave("-");
13253         gameInfo.white = StrSave(first.tidy);
13254         gameInfo.black = StrSave(UserName());
13255         gameInfo.timeControl = TimeControlTagValue();
13256         break;
13257
13258       case MachinePlaysBlack:
13259         gameInfo.event = StrSave( appData.pgnEventHeader );
13260         gameInfo.site = StrSave(HostName());
13261         gameInfo.date = PGNDate();
13262         gameInfo.round = StrSave("-");
13263         gameInfo.white = StrSave(UserName());
13264         gameInfo.black = StrSave(first.tidy);
13265         gameInfo.timeControl = TimeControlTagValue();
13266         break;
13267
13268       case TwoMachinesPlay:
13269         gameInfo.event = StrSave( appData.pgnEventHeader );
13270         gameInfo.site = StrSave(HostName());
13271         gameInfo.date = PGNDate();
13272         if (matchGame > 0) {
13273             char buf[MSG_SIZ];
13274             snprintf(buf, MSG_SIZ, "%d", matchGame);
13275             gameInfo.round = StrSave(buf);
13276         } else {
13277             gameInfo.round = StrSave("-");
13278         }
13279         if (first.twoMachinesColor[0] == 'w') {
13280             gameInfo.white = StrSave(first.tidy);
13281             gameInfo.black = StrSave(second.tidy);
13282         } else {
13283             gameInfo.white = StrSave(second.tidy);
13284             gameInfo.black = StrSave(first.tidy);
13285         }
13286         gameInfo.timeControl = TimeControlTagValue();
13287         break;
13288
13289       case EditGame:
13290         gameInfo.event = StrSave("Edited game");
13291         gameInfo.site = StrSave(HostName());
13292         gameInfo.date = PGNDate();
13293         gameInfo.round = StrSave("-");
13294         gameInfo.white = StrSave("-");
13295         gameInfo.black = StrSave("-");
13296         gameInfo.result = r;
13297         gameInfo.resultDetails = p;
13298         break;
13299
13300       case EditPosition:
13301         gameInfo.event = StrSave("Edited position");
13302         gameInfo.site = StrSave(HostName());
13303         gameInfo.date = PGNDate();
13304         gameInfo.round = StrSave("-");
13305         gameInfo.white = StrSave("-");
13306         gameInfo.black = StrSave("-");
13307         break;
13308
13309       case IcsPlayingWhite:
13310       case IcsPlayingBlack:
13311       case IcsObserving:
13312       case IcsExamining:
13313         break;
13314
13315       case PlayFromGameFile:
13316         gameInfo.event = StrSave("Game from non-PGN file");
13317         gameInfo.site = StrSave(HostName());
13318         gameInfo.date = PGNDate();
13319         gameInfo.round = StrSave("-");
13320         gameInfo.white = StrSave("?");
13321         gameInfo.black = StrSave("?");
13322         break;
13323
13324       default:
13325         break;
13326     }
13327 }
13328
13329 void
13330 ReplaceComment(index, text)
13331      int index;
13332      char *text;
13333 {
13334     int len;
13335     char *p;
13336     float score;
13337
13338     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13339        pvInfoList[index-1].depth == len &&
13340        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13341        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13342     while (*text == '\n') text++;
13343     len = strlen(text);
13344     while (len > 0 && text[len - 1] == '\n') len--;
13345
13346     if (commentList[index] != NULL)
13347       free(commentList[index]);
13348
13349     if (len == 0) {
13350         commentList[index] = NULL;
13351         return;
13352     }
13353   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13354       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13355       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13356     commentList[index] = (char *) malloc(len + 2);
13357     strncpy(commentList[index], text, len);
13358     commentList[index][len] = '\n';
13359     commentList[index][len + 1] = NULLCHAR;
13360   } else {
13361     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13362     char *p;
13363     commentList[index] = (char *) malloc(len + 7);
13364     safeStrCpy(commentList[index], "{\n", 3);
13365     safeStrCpy(commentList[index]+2, text, len+1);
13366     commentList[index][len+2] = NULLCHAR;
13367     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13368     strcat(commentList[index], "\n}\n");
13369   }
13370 }
13371
13372 void
13373 CrushCRs(text)
13374      char *text;
13375 {
13376   char *p = text;
13377   char *q = text;
13378   char ch;
13379
13380   do {
13381     ch = *p++;
13382     if (ch == '\r') continue;
13383     *q++ = ch;
13384   } while (ch != '\0');
13385 }
13386
13387 void
13388 AppendComment(index, text, addBraces)
13389      int index;
13390      char *text;
13391      Boolean addBraces; // [HGM] braces: tells if we should add {}
13392 {
13393     int oldlen, len;
13394     char *old;
13395
13396 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13397     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13398
13399     CrushCRs(text);
13400     while (*text == '\n') text++;
13401     len = strlen(text);
13402     while (len > 0 && text[len - 1] == '\n') len--;
13403
13404     if (len == 0) return;
13405
13406     if (commentList[index] != NULL) {
13407         old = commentList[index];
13408         oldlen = strlen(old);
13409         while(commentList[index][oldlen-1] ==  '\n')
13410           commentList[index][--oldlen] = NULLCHAR;
13411         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13412         safeStrCpy(commentList[index], old, oldlen + len + 6);
13413         free(old);
13414         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13415         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13416           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13417           while (*text == '\n') { text++; len--; }
13418           commentList[index][--oldlen] = NULLCHAR;
13419       }
13420         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13421         else          strcat(commentList[index], "\n");
13422         strcat(commentList[index], text);
13423         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13424         else          strcat(commentList[index], "\n");
13425     } else {
13426         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13427         if(addBraces)
13428           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13429         else commentList[index][0] = NULLCHAR;
13430         strcat(commentList[index], text);
13431         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13432         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13433     }
13434 }
13435
13436 static char * FindStr( char * text, char * sub_text )
13437 {
13438     char * result = strstr( text, sub_text );
13439
13440     if( result != NULL ) {
13441         result += strlen( sub_text );
13442     }
13443
13444     return result;
13445 }
13446
13447 /* [AS] Try to extract PV info from PGN comment */
13448 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13449 char *GetInfoFromComment( int index, char * text )
13450 {
13451     char * sep = text, *p;
13452
13453     if( text != NULL && index > 0 ) {
13454         int score = 0;
13455         int depth = 0;
13456         int time = -1, sec = 0, deci;
13457         char * s_eval = FindStr( text, "[%eval " );
13458         char * s_emt = FindStr( text, "[%emt " );
13459
13460         if( s_eval != NULL || s_emt != NULL ) {
13461             /* New style */
13462             char delim;
13463
13464             if( s_eval != NULL ) {
13465                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13466                     return text;
13467                 }
13468
13469                 if( delim != ']' ) {
13470                     return text;
13471                 }
13472             }
13473
13474             if( s_emt != NULL ) {
13475             }
13476                 return text;
13477         }
13478         else {
13479             /* We expect something like: [+|-]nnn.nn/dd */
13480             int score_lo = 0;
13481
13482             if(*text != '{') return text; // [HGM] braces: must be normal comment
13483
13484             sep = strchr( text, '/' );
13485             if( sep == NULL || sep < (text+4) ) {
13486                 return text;
13487             }
13488
13489             p = text;
13490             if(p[1] == '(') { // comment starts with PV
13491                p = strchr(p, ')'); // locate end of PV
13492                if(p == NULL || sep < p+5) return text;
13493                // at this point we have something like "{(.*) +0.23/6 ..."
13494                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13495                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13496                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13497             }
13498             time = -1; sec = -1; deci = -1;
13499             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13500                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13501                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13502                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13503                 return text;
13504             }
13505
13506             if( score_lo < 0 || score_lo >= 100 ) {
13507                 return text;
13508             }
13509
13510             if(sec >= 0) time = 600*time + 10*sec; else
13511             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13512
13513             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13514
13515             /* [HGM] PV time: now locate end of PV info */
13516             while( *++sep >= '0' && *sep <= '9'); // strip depth
13517             if(time >= 0)
13518             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13519             if(sec >= 0)
13520             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13521             if(deci >= 0)
13522             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13523             while(*sep == ' ') sep++;
13524         }
13525
13526         if( depth <= 0 ) {
13527             return text;
13528         }
13529
13530         if( time < 0 ) {
13531             time = -1;
13532         }
13533
13534         pvInfoList[index-1].depth = depth;
13535         pvInfoList[index-1].score = score;
13536         pvInfoList[index-1].time  = 10*time; // centi-sec
13537         if(*sep == '}') *sep = 0; else *--sep = '{';
13538         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13539     }
13540     return sep;
13541 }
13542
13543 void
13544 SendToProgram(message, cps)
13545      char *message;
13546      ChessProgramState *cps;
13547 {
13548     int count, outCount, error;
13549     char buf[MSG_SIZ];
13550
13551     if (cps->pr == NULL) return;
13552     Attention(cps);
13553
13554     if (appData.debugMode) {
13555         TimeMark now;
13556         GetTimeMark(&now);
13557         fprintf(debugFP, "%ld >%-6s: %s",
13558                 SubtractTimeMarks(&now, &programStartTime),
13559                 cps->which, message);
13560     }
13561
13562     count = strlen(message);
13563     outCount = OutputToProcess(cps->pr, message, count, &error);
13564     if (outCount < count && !exiting
13565                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13566       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13567         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13568             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13569                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13570                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13571             } else {
13572                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13573             }
13574             gameInfo.resultDetails = StrSave(buf);
13575         }
13576         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13577     }
13578 }
13579
13580 void
13581 ReceiveFromProgram(isr, closure, message, count, error)
13582      InputSourceRef isr;
13583      VOIDSTAR closure;
13584      char *message;
13585      int count;
13586      int error;
13587 {
13588     char *end_str;
13589     char buf[MSG_SIZ];
13590     ChessProgramState *cps = (ChessProgramState *)closure;
13591
13592     if (isr != cps->isr) return; /* Killed intentionally */
13593     if (count <= 0) {
13594         if (count == 0) {
13595             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13596                     cps->which, cps->program);
13597         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13598                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13599                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13600                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13601                 } else {
13602                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13603                 }
13604                 gameInfo.resultDetails = StrSave(buf);
13605             }
13606             RemoveInputSource(cps->isr);
13607             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13608         } else {
13609             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13610                     cps->which, cps->program);
13611             RemoveInputSource(cps->isr);
13612
13613             /* [AS] Program is misbehaving badly... kill it */
13614             if( count == -2 ) {
13615                 DestroyChildProcess( cps->pr, 9 );
13616                 cps->pr = NoProc;
13617             }
13618
13619             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13620         }
13621         return;
13622     }
13623
13624     if ((end_str = strchr(message, '\r')) != NULL)
13625       *end_str = NULLCHAR;
13626     if ((end_str = strchr(message, '\n')) != NULL)
13627       *end_str = NULLCHAR;
13628
13629     if (appData.debugMode) {
13630         TimeMark now; int print = 1;
13631         char *quote = ""; char c; int i;
13632
13633         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13634                 char start = message[0];
13635                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13636                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13637                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13638                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13639                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13640                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13641                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13642                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13643                    sscanf(message, "hint: %c", &c)!=1 && 
13644                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13645                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13646                     print = (appData.engineComments >= 2);
13647                 }
13648                 message[0] = start; // restore original message
13649         }
13650         if(print) {
13651                 GetTimeMark(&now);
13652                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13653                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13654                         quote,
13655                         message);
13656         }
13657     }
13658
13659     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13660     if (appData.icsEngineAnalyze) {
13661         if (strstr(message, "whisper") != NULL ||
13662              strstr(message, "kibitz") != NULL ||
13663             strstr(message, "tellics") != NULL) return;
13664     }
13665
13666     HandleMachineMove(message, cps);
13667 }
13668
13669
13670 void
13671 SendTimeControl(cps, mps, tc, inc, sd, st)
13672      ChessProgramState *cps;
13673      int mps, inc, sd, st;
13674      long tc;
13675 {
13676     char buf[MSG_SIZ];
13677     int seconds;
13678
13679     if( timeControl_2 > 0 ) {
13680         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13681             tc = timeControl_2;
13682         }
13683     }
13684     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13685     inc /= cps->timeOdds;
13686     st  /= cps->timeOdds;
13687
13688     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13689
13690     if (st > 0) {
13691       /* Set exact time per move, normally using st command */
13692       if (cps->stKludge) {
13693         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13694         seconds = st % 60;
13695         if (seconds == 0) {
13696           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13697         } else {
13698           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13699         }
13700       } else {
13701         snprintf(buf, MSG_SIZ, "st %d\n", st);
13702       }
13703     } else {
13704       /* Set conventional or incremental time control, using level command */
13705       if (seconds == 0) {
13706         /* Note old gnuchess bug -- minutes:seconds used to not work.
13707            Fixed in later versions, but still avoid :seconds
13708            when seconds is 0. */
13709         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13710       } else {
13711         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13712                  seconds, inc/1000.);
13713       }
13714     }
13715     SendToProgram(buf, cps);
13716
13717     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13718     /* Orthogonally, limit search to given depth */
13719     if (sd > 0) {
13720       if (cps->sdKludge) {
13721         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13722       } else {
13723         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13724       }
13725       SendToProgram(buf, cps);
13726     }
13727
13728     if(cps->nps > 0) { /* [HGM] nps */
13729         if(cps->supportsNPS == FALSE)
13730           cps->nps = -1; // don't use if engine explicitly says not supported!
13731         else {
13732           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13733           SendToProgram(buf, cps);
13734         }
13735     }
13736 }
13737
13738 ChessProgramState *WhitePlayer()
13739 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13740 {
13741     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13742        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13743         return &second;
13744     return &first;
13745 }
13746
13747 void
13748 SendTimeRemaining(cps, machineWhite)
13749      ChessProgramState *cps;
13750      int /*boolean*/ machineWhite;
13751 {
13752     char message[MSG_SIZ];
13753     long time, otime;
13754
13755     /* Note: this routine must be called when the clocks are stopped
13756        or when they have *just* been set or switched; otherwise
13757        it will be off by the time since the current tick started.
13758     */
13759     if (machineWhite) {
13760         time = whiteTimeRemaining / 10;
13761         otime = blackTimeRemaining / 10;
13762     } else {
13763         time = blackTimeRemaining / 10;
13764         otime = whiteTimeRemaining / 10;
13765     }
13766     /* [HGM] translate opponent's time by time-odds factor */
13767     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13768     if (appData.debugMode) {
13769         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13770     }
13771
13772     if (time <= 0) time = 1;
13773     if (otime <= 0) otime = 1;
13774
13775     snprintf(message, MSG_SIZ, "time %ld\n", time);
13776     SendToProgram(message, cps);
13777
13778     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13779     SendToProgram(message, cps);
13780 }
13781
13782 int
13783 BoolFeature(p, name, loc, cps)
13784      char **p;
13785      char *name;
13786      int *loc;
13787      ChessProgramState *cps;
13788 {
13789   char buf[MSG_SIZ];
13790   int len = strlen(name);
13791   int val;
13792
13793   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13794     (*p) += len + 1;
13795     sscanf(*p, "%d", &val);
13796     *loc = (val != 0);
13797     while (**p && **p != ' ')
13798       (*p)++;
13799     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13800     SendToProgram(buf, cps);
13801     return TRUE;
13802   }
13803   return FALSE;
13804 }
13805
13806 int
13807 IntFeature(p, name, loc, cps)
13808      char **p;
13809      char *name;
13810      int *loc;
13811      ChessProgramState *cps;
13812 {
13813   char buf[MSG_SIZ];
13814   int len = strlen(name);
13815   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13816     (*p) += len + 1;
13817     sscanf(*p, "%d", loc);
13818     while (**p && **p != ' ') (*p)++;
13819     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13820     SendToProgram(buf, cps);
13821     return TRUE;
13822   }
13823   return FALSE;
13824 }
13825
13826 int
13827 StringFeature(p, name, loc, cps)
13828      char **p;
13829      char *name;
13830      char loc[];
13831      ChessProgramState *cps;
13832 {
13833   char buf[MSG_SIZ];
13834   int len = strlen(name);
13835   if (strncmp((*p), name, len) == 0
13836       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13837     (*p) += len + 2;
13838     sscanf(*p, "%[^\"]", loc);
13839     while (**p && **p != '\"') (*p)++;
13840     if (**p == '\"') (*p)++;
13841     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13842     SendToProgram(buf, cps);
13843     return TRUE;
13844   }
13845   return FALSE;
13846 }
13847
13848 int
13849 ParseOption(Option *opt, ChessProgramState *cps)
13850 // [HGM] options: process the string that defines an engine option, and determine
13851 // name, type, default value, and allowed value range
13852 {
13853         char *p, *q, buf[MSG_SIZ];
13854         int n, min = (-1)<<31, max = 1<<31, def;
13855
13856         if(p = strstr(opt->name, " -spin ")) {
13857             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13858             if(max < min) max = min; // enforce consistency
13859             if(def < min) def = min;
13860             if(def > max) def = max;
13861             opt->value = def;
13862             opt->min = min;
13863             opt->max = max;
13864             opt->type = Spin;
13865         } else if((p = strstr(opt->name, " -slider "))) {
13866             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13867             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13868             if(max < min) max = min; // enforce consistency
13869             if(def < min) def = min;
13870             if(def > max) def = max;
13871             opt->value = def;
13872             opt->min = min;
13873             opt->max = max;
13874             opt->type = Spin; // Slider;
13875         } else if((p = strstr(opt->name, " -string "))) {
13876             opt->textValue = p+9;
13877             opt->type = TextBox;
13878         } else if((p = strstr(opt->name, " -file "))) {
13879             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13880             opt->textValue = p+7;
13881             opt->type = TextBox; // FileName;
13882         } else if((p = strstr(opt->name, " -path "))) {
13883             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13884             opt->textValue = p+7;
13885             opt->type = TextBox; // PathName;
13886         } else if(p = strstr(opt->name, " -check ")) {
13887             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13888             opt->value = (def != 0);
13889             opt->type = CheckBox;
13890         } else if(p = strstr(opt->name, " -combo ")) {
13891             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13892             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13893             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13894             opt->value = n = 0;
13895             while(q = StrStr(q, " /// ")) {
13896                 n++; *q = 0;    // count choices, and null-terminate each of them
13897                 q += 5;
13898                 if(*q == '*') { // remember default, which is marked with * prefix
13899                     q++;
13900                     opt->value = n;
13901                 }
13902                 cps->comboList[cps->comboCnt++] = q;
13903             }
13904             cps->comboList[cps->comboCnt++] = NULL;
13905             opt->max = n + 1;
13906             opt->type = ComboBox;
13907         } else if(p = strstr(opt->name, " -button")) {
13908             opt->type = Button;
13909         } else if(p = strstr(opt->name, " -save")) {
13910             opt->type = SaveButton;
13911         } else return FALSE;
13912         *p = 0; // terminate option name
13913         // now look if the command-line options define a setting for this engine option.
13914         if(cps->optionSettings && cps->optionSettings[0])
13915             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13916         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13917           snprintf(buf, MSG_SIZ, "option %s", p);
13918                 if(p = strstr(buf, ",")) *p = 0;
13919                 if(q = strchr(buf, '=')) switch(opt->type) {
13920                     case ComboBox:
13921                         for(n=0; n<opt->max; n++)
13922                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13923                         break;
13924                     case TextBox:
13925                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13926                         break;
13927                     case Spin:
13928                     case CheckBox:
13929                         opt->value = atoi(q+1);
13930                     default:
13931                         break;
13932                 }
13933                 strcat(buf, "\n");
13934                 SendToProgram(buf, cps);
13935         }
13936         return TRUE;
13937 }
13938
13939 void
13940 FeatureDone(cps, val)
13941      ChessProgramState* cps;
13942      int val;
13943 {
13944   DelayedEventCallback cb = GetDelayedEvent();
13945   if ((cb == InitBackEnd3 && cps == &first) ||
13946       (cb == SettingsMenuIfReady && cps == &second) ||
13947       (cb == TwoMachinesEventIfReady && cps == &second)) {
13948     CancelDelayedEvent();
13949     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13950   }
13951   cps->initDone = val;
13952 }
13953
13954 /* Parse feature command from engine */
13955 void
13956 ParseFeatures(args, cps)
13957      char* args;
13958      ChessProgramState *cps;
13959 {
13960   char *p = args;
13961   char *q;
13962   int val;
13963   char buf[MSG_SIZ];
13964
13965   for (;;) {
13966     while (*p == ' ') p++;
13967     if (*p == NULLCHAR) return;
13968
13969     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13970     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13971     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13972     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13973     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13974     if (BoolFeature(&p, "reuse", &val, cps)) {
13975       /* Engine can disable reuse, but can't enable it if user said no */
13976       if (!val) cps->reuse = FALSE;
13977       continue;
13978     }
13979     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13980     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13981       if (gameMode == TwoMachinesPlay) {
13982         DisplayTwoMachinesTitle();
13983       } else {
13984         DisplayTitle("");
13985       }
13986       continue;
13987     }
13988     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13989     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13990     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13991     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13992     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13993     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13994     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13995     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13996     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13997     if (IntFeature(&p, "done", &val, cps)) {
13998       FeatureDone(cps, val);
13999       continue;
14000     }
14001     /* Added by Tord: */
14002     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14003     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14004     /* End of additions by Tord */
14005
14006     /* [HGM] added features: */
14007     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14008     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14009     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14010     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14011     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14012     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14013     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14014         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14015           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14016             SendToProgram(buf, cps);
14017             continue;
14018         }
14019         if(cps->nrOptions >= MAX_OPTIONS) {
14020             cps->nrOptions--;
14021             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
14022             DisplayError(buf, 0);
14023         }
14024         continue;
14025     }
14026     /* End of additions by HGM */
14027
14028     /* unknown feature: complain and skip */
14029     q = p;
14030     while (*q && *q != '=') q++;
14031     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14032     SendToProgram(buf, cps);
14033     p = q;
14034     if (*p == '=') {
14035       p++;
14036       if (*p == '\"') {
14037         p++;
14038         while (*p && *p != '\"') p++;
14039         if (*p == '\"') p++;
14040       } else {
14041         while (*p && *p != ' ') p++;
14042       }
14043     }
14044   }
14045
14046 }
14047
14048 void
14049 PeriodicUpdatesEvent(newState)
14050      int newState;
14051 {
14052     if (newState == appData.periodicUpdates)
14053       return;
14054
14055     appData.periodicUpdates=newState;
14056
14057     /* Display type changes, so update it now */
14058 //    DisplayAnalysis();
14059
14060     /* Get the ball rolling again... */
14061     if (newState) {
14062         AnalysisPeriodicEvent(1);
14063         StartAnalysisClock();
14064     }
14065 }
14066
14067 void
14068 PonderNextMoveEvent(newState)
14069      int newState;
14070 {
14071     if (newState == appData.ponderNextMove) return;
14072     if (gameMode == EditPosition) EditPositionDone(TRUE);
14073     if (newState) {
14074         SendToProgram("hard\n", &first);
14075         if (gameMode == TwoMachinesPlay) {
14076             SendToProgram("hard\n", &second);
14077         }
14078     } else {
14079         SendToProgram("easy\n", &first);
14080         thinkOutput[0] = NULLCHAR;
14081         if (gameMode == TwoMachinesPlay) {
14082             SendToProgram("easy\n", &second);
14083         }
14084     }
14085     appData.ponderNextMove = newState;
14086 }
14087
14088 void
14089 NewSettingEvent(option, feature, command, value)
14090      char *command;
14091      int option, value, *feature;
14092 {
14093     char buf[MSG_SIZ];
14094
14095     if (gameMode == EditPosition) EditPositionDone(TRUE);
14096     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14097     if(feature == NULL || *feature) SendToProgram(buf, &first);
14098     if (gameMode == TwoMachinesPlay) {
14099         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14100     }
14101 }
14102
14103 void
14104 ShowThinkingEvent()
14105 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14106 {
14107     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14108     int newState = appData.showThinking
14109         // [HGM] thinking: other features now need thinking output as well
14110         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14111
14112     if (oldState == newState) return;
14113     oldState = newState;
14114     if (gameMode == EditPosition) EditPositionDone(TRUE);
14115     if (oldState) {
14116         SendToProgram("post\n", &first);
14117         if (gameMode == TwoMachinesPlay) {
14118             SendToProgram("post\n", &second);
14119         }
14120     } else {
14121         SendToProgram("nopost\n", &first);
14122         thinkOutput[0] = NULLCHAR;
14123         if (gameMode == TwoMachinesPlay) {
14124             SendToProgram("nopost\n", &second);
14125         }
14126     }
14127 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14128 }
14129
14130 void
14131 AskQuestionEvent(title, question, replyPrefix, which)
14132      char *title; char *question; char *replyPrefix; char *which;
14133 {
14134   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14135   if (pr == NoProc) return;
14136   AskQuestion(title, question, replyPrefix, pr);
14137 }
14138
14139 void
14140 DisplayMove(moveNumber)
14141      int moveNumber;
14142 {
14143     char message[MSG_SIZ];
14144     char res[MSG_SIZ];
14145     char cpThinkOutput[MSG_SIZ];
14146
14147     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14148
14149     if (moveNumber == forwardMostMove - 1 ||
14150         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14151
14152         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14153
14154         if (strchr(cpThinkOutput, '\n')) {
14155             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14156         }
14157     } else {
14158         *cpThinkOutput = NULLCHAR;
14159     }
14160
14161     /* [AS] Hide thinking from human user */
14162     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14163         *cpThinkOutput = NULLCHAR;
14164         if( thinkOutput[0] != NULLCHAR ) {
14165             int i;
14166
14167             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14168                 cpThinkOutput[i] = '.';
14169             }
14170             cpThinkOutput[i] = NULLCHAR;
14171             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14172         }
14173     }
14174
14175     if (moveNumber == forwardMostMove - 1 &&
14176         gameInfo.resultDetails != NULL) {
14177         if (gameInfo.resultDetails[0] == NULLCHAR) {
14178           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14179         } else {
14180           snprintf(res, MSG_SIZ, " {%s} %s",
14181                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14182         }
14183     } else {
14184         res[0] = NULLCHAR;
14185     }
14186
14187     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14188         DisplayMessage(res, cpThinkOutput);
14189     } else {
14190       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14191                 WhiteOnMove(moveNumber) ? " " : ".. ",
14192                 parseList[moveNumber], res);
14193         DisplayMessage(message, cpThinkOutput);
14194     }
14195 }
14196
14197 void
14198 DisplayComment(moveNumber, text)
14199      int moveNumber;
14200      char *text;
14201 {
14202     char title[MSG_SIZ];
14203     char buf[8000]; // comment can be long!
14204     int score, depth;
14205
14206     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14207       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14208     } else {
14209       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14210               WhiteOnMove(moveNumber) ? " " : ".. ",
14211               parseList[moveNumber]);
14212     }
14213     // [HGM] PV info: display PV info together with (or as) comment
14214     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14215       if(text == NULL) text = "";
14216       score = pvInfoList[moveNumber].score;
14217       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14218               depth, (pvInfoList[moveNumber].time+50)/100, text);
14219       text = buf;
14220     }
14221     if (text != NULL && (appData.autoDisplayComment || commentUp))
14222         CommentPopUp(title, text);
14223 }
14224
14225 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14226  * might be busy thinking or pondering.  It can be omitted if your
14227  * gnuchess is configured to stop thinking immediately on any user
14228  * input.  However, that gnuchess feature depends on the FIONREAD
14229  * ioctl, which does not work properly on some flavors of Unix.
14230  */
14231 void
14232 Attention(cps)
14233      ChessProgramState *cps;
14234 {
14235 #if ATTENTION
14236     if (!cps->useSigint) return;
14237     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14238     switch (gameMode) {
14239       case MachinePlaysWhite:
14240       case MachinePlaysBlack:
14241       case TwoMachinesPlay:
14242       case IcsPlayingWhite:
14243       case IcsPlayingBlack:
14244       case AnalyzeMode:
14245       case AnalyzeFile:
14246         /* Skip if we know it isn't thinking */
14247         if (!cps->maybeThinking) return;
14248         if (appData.debugMode)
14249           fprintf(debugFP, "Interrupting %s\n", cps->which);
14250         InterruptChildProcess(cps->pr);
14251         cps->maybeThinking = FALSE;
14252         break;
14253       default:
14254         break;
14255     }
14256 #endif /*ATTENTION*/
14257 }
14258
14259 int
14260 CheckFlags()
14261 {
14262     if (whiteTimeRemaining <= 0) {
14263         if (!whiteFlag) {
14264             whiteFlag = TRUE;
14265             if (appData.icsActive) {
14266                 if (appData.autoCallFlag &&
14267                     gameMode == IcsPlayingBlack && !blackFlag) {
14268                   SendToICS(ics_prefix);
14269                   SendToICS("flag\n");
14270                 }
14271             } else {
14272                 if (blackFlag) {
14273                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14274                 } else {
14275                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14276                     if (appData.autoCallFlag) {
14277                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14278                         return TRUE;
14279                     }
14280                 }
14281             }
14282         }
14283     }
14284     if (blackTimeRemaining <= 0) {
14285         if (!blackFlag) {
14286             blackFlag = TRUE;
14287             if (appData.icsActive) {
14288                 if (appData.autoCallFlag &&
14289                     gameMode == IcsPlayingWhite && !whiteFlag) {
14290                   SendToICS(ics_prefix);
14291                   SendToICS("flag\n");
14292                 }
14293             } else {
14294                 if (whiteFlag) {
14295                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14296                 } else {
14297                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14298                     if (appData.autoCallFlag) {
14299                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14300                         return TRUE;
14301                     }
14302                 }
14303             }
14304         }
14305     }
14306     return FALSE;
14307 }
14308
14309 void
14310 CheckTimeControl()
14311 {
14312     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14313         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14314
14315     /*
14316      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14317      */
14318     if ( !WhiteOnMove(forwardMostMove) ) {
14319         /* White made time control */
14320         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14321         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14322         /* [HGM] time odds: correct new time quota for time odds! */
14323                                             / WhitePlayer()->timeOdds;
14324         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14325     } else {
14326         lastBlack -= blackTimeRemaining;
14327         /* Black made time control */
14328         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14329                                             / WhitePlayer()->other->timeOdds;
14330         lastWhite = whiteTimeRemaining;
14331     }
14332 }
14333
14334 void
14335 DisplayBothClocks()
14336 {
14337     int wom = gameMode == EditPosition ?
14338       !blackPlaysFirst : WhiteOnMove(currentMove);
14339     DisplayWhiteClock(whiteTimeRemaining, wom);
14340     DisplayBlackClock(blackTimeRemaining, !wom);
14341 }
14342
14343
14344 /* Timekeeping seems to be a portability nightmare.  I think everyone
14345    has ftime(), but I'm really not sure, so I'm including some ifdefs
14346    to use other calls if you don't.  Clocks will be less accurate if
14347    you have neither ftime nor gettimeofday.
14348 */
14349
14350 /* VS 2008 requires the #include outside of the function */
14351 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14352 #include <sys/timeb.h>
14353 #endif
14354
14355 /* Get the current time as a TimeMark */
14356 void
14357 GetTimeMark(tm)
14358      TimeMark *tm;
14359 {
14360 #if HAVE_GETTIMEOFDAY
14361
14362     struct timeval timeVal;
14363     struct timezone timeZone;
14364
14365     gettimeofday(&timeVal, &timeZone);
14366     tm->sec = (long) timeVal.tv_sec;
14367     tm->ms = (int) (timeVal.tv_usec / 1000L);
14368
14369 #else /*!HAVE_GETTIMEOFDAY*/
14370 #if HAVE_FTIME
14371
14372 // include <sys/timeb.h> / moved to just above start of function
14373     struct timeb timeB;
14374
14375     ftime(&timeB);
14376     tm->sec = (long) timeB.time;
14377     tm->ms = (int) timeB.millitm;
14378
14379 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14380     tm->sec = (long) time(NULL);
14381     tm->ms = 0;
14382 #endif
14383 #endif
14384 }
14385
14386 /* Return the difference in milliseconds between two
14387    time marks.  We assume the difference will fit in a long!
14388 */
14389 long
14390 SubtractTimeMarks(tm2, tm1)
14391      TimeMark *tm2, *tm1;
14392 {
14393     return 1000L*(tm2->sec - tm1->sec) +
14394            (long) (tm2->ms - tm1->ms);
14395 }
14396
14397
14398 /*
14399  * Code to manage the game clocks.
14400  *
14401  * In tournament play, black starts the clock and then white makes a move.
14402  * We give the human user a slight advantage if he is playing white---the
14403  * clocks don't run until he makes his first move, so it takes zero time.
14404  * Also, we don't account for network lag, so we could get out of sync
14405  * with GNU Chess's clock -- but then, referees are always right.
14406  */
14407
14408 static TimeMark tickStartTM;
14409 static long intendedTickLength;
14410
14411 long
14412 NextTickLength(timeRemaining)
14413      long timeRemaining;
14414 {
14415     long nominalTickLength, nextTickLength;
14416
14417     if (timeRemaining > 0L && timeRemaining <= 10000L)
14418       nominalTickLength = 100L;
14419     else
14420       nominalTickLength = 1000L;
14421     nextTickLength = timeRemaining % nominalTickLength;
14422     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14423
14424     return nextTickLength;
14425 }
14426
14427 /* Adjust clock one minute up or down */
14428 void
14429 AdjustClock(Boolean which, int dir)
14430 {
14431     if(which) blackTimeRemaining += 60000*dir;
14432     else      whiteTimeRemaining += 60000*dir;
14433     DisplayBothClocks();
14434 }
14435
14436 /* Stop clocks and reset to a fresh time control */
14437 void
14438 ResetClocks()
14439 {
14440     (void) StopClockTimer();
14441     if (appData.icsActive) {
14442         whiteTimeRemaining = blackTimeRemaining = 0;
14443     } else if (searchTime) {
14444         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14445         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14446     } else { /* [HGM] correct new time quote for time odds */
14447         whiteTC = blackTC = fullTimeControlString;
14448         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14449         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14450     }
14451     if (whiteFlag || blackFlag) {
14452         DisplayTitle("");
14453         whiteFlag = blackFlag = FALSE;
14454     }
14455     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14456     DisplayBothClocks();
14457 }
14458
14459 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14460
14461 /* Decrement running clock by amount of time that has passed */
14462 void
14463 DecrementClocks()
14464 {
14465     long timeRemaining;
14466     long lastTickLength, fudge;
14467     TimeMark now;
14468
14469     if (!appData.clockMode) return;
14470     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14471
14472     GetTimeMark(&now);
14473
14474     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14475
14476     /* Fudge if we woke up a little too soon */
14477     fudge = intendedTickLength - lastTickLength;
14478     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14479
14480     if (WhiteOnMove(forwardMostMove)) {
14481         if(whiteNPS >= 0) lastTickLength = 0;
14482         timeRemaining = whiteTimeRemaining -= lastTickLength;
14483         if(timeRemaining < 0 && !appData.icsActive) {
14484             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14485             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14486                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14487                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14488             }
14489         }
14490         DisplayWhiteClock(whiteTimeRemaining - fudge,
14491                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14492     } else {
14493         if(blackNPS >= 0) lastTickLength = 0;
14494         timeRemaining = blackTimeRemaining -= lastTickLength;
14495         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14496             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14497             if(suddenDeath) {
14498                 blackStartMove = forwardMostMove;
14499                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14500             }
14501         }
14502         DisplayBlackClock(blackTimeRemaining - fudge,
14503                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14504     }
14505     if (CheckFlags()) return;
14506
14507     tickStartTM = now;
14508     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14509     StartClockTimer(intendedTickLength);
14510
14511     /* if the time remaining has fallen below the alarm threshold, sound the
14512      * alarm. if the alarm has sounded and (due to a takeback or time control
14513      * with increment) the time remaining has increased to a level above the
14514      * threshold, reset the alarm so it can sound again.
14515      */
14516
14517     if (appData.icsActive && appData.icsAlarm) {
14518
14519         /* make sure we are dealing with the user's clock */
14520         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14521                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14522            )) return;
14523
14524         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14525             alarmSounded = FALSE;
14526         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14527             PlayAlarmSound();
14528             alarmSounded = TRUE;
14529         }
14530     }
14531 }
14532
14533
14534 /* A player has just moved, so stop the previously running
14535    clock and (if in clock mode) start the other one.
14536    We redisplay both clocks in case we're in ICS mode, because
14537    ICS gives us an update to both clocks after every move.
14538    Note that this routine is called *after* forwardMostMove
14539    is updated, so the last fractional tick must be subtracted
14540    from the color that is *not* on move now.
14541 */
14542 void
14543 SwitchClocks(int newMoveNr)
14544 {
14545     long lastTickLength;
14546     TimeMark now;
14547     int flagged = FALSE;
14548
14549     GetTimeMark(&now);
14550
14551     if (StopClockTimer() && appData.clockMode) {
14552         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14553         if (!WhiteOnMove(forwardMostMove)) {
14554             if(blackNPS >= 0) lastTickLength = 0;
14555             blackTimeRemaining -= lastTickLength;
14556            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14557 //         if(pvInfoList[forwardMostMove].time == -1)
14558                  pvInfoList[forwardMostMove].time =               // use GUI time
14559                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14560         } else {
14561            if(whiteNPS >= 0) lastTickLength = 0;
14562            whiteTimeRemaining -= lastTickLength;
14563            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14564 //         if(pvInfoList[forwardMostMove].time == -1)
14565                  pvInfoList[forwardMostMove].time =
14566                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14567         }
14568         flagged = CheckFlags();
14569     }
14570     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14571     CheckTimeControl();
14572
14573     if (flagged || !appData.clockMode) return;
14574
14575     switch (gameMode) {
14576       case MachinePlaysBlack:
14577       case MachinePlaysWhite:
14578       case BeginningOfGame:
14579         if (pausing) return;
14580         break;
14581
14582       case EditGame:
14583       case PlayFromGameFile:
14584       case IcsExamining:
14585         return;
14586
14587       default:
14588         break;
14589     }
14590
14591     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14592         if(WhiteOnMove(forwardMostMove))
14593              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14594         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14595     }
14596
14597     tickStartTM = now;
14598     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14599       whiteTimeRemaining : blackTimeRemaining);
14600     StartClockTimer(intendedTickLength);
14601 }
14602
14603
14604 /* Stop both clocks */
14605 void
14606 StopClocks()
14607 {
14608     long lastTickLength;
14609     TimeMark now;
14610
14611     if (!StopClockTimer()) return;
14612     if (!appData.clockMode) return;
14613
14614     GetTimeMark(&now);
14615
14616     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14617     if (WhiteOnMove(forwardMostMove)) {
14618         if(whiteNPS >= 0) lastTickLength = 0;
14619         whiteTimeRemaining -= lastTickLength;
14620         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14621     } else {
14622         if(blackNPS >= 0) lastTickLength = 0;
14623         blackTimeRemaining -= lastTickLength;
14624         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14625     }
14626     CheckFlags();
14627 }
14628
14629 /* Start clock of player on move.  Time may have been reset, so
14630    if clock is already running, stop and restart it. */
14631 void
14632 StartClocks()
14633 {
14634     (void) StopClockTimer(); /* in case it was running already */
14635     DisplayBothClocks();
14636     if (CheckFlags()) return;
14637
14638     if (!appData.clockMode) return;
14639     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14640
14641     GetTimeMark(&tickStartTM);
14642     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14643       whiteTimeRemaining : blackTimeRemaining);
14644
14645    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14646     whiteNPS = blackNPS = -1;
14647     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14648        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14649         whiteNPS = first.nps;
14650     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14651        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14652         blackNPS = first.nps;
14653     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14654         whiteNPS = second.nps;
14655     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14656         blackNPS = second.nps;
14657     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14658
14659     StartClockTimer(intendedTickLength);
14660 }
14661
14662 char *
14663 TimeString(ms)
14664      long ms;
14665 {
14666     long second, minute, hour, day;
14667     char *sign = "";
14668     static char buf[32];
14669
14670     if (ms > 0 && ms <= 9900) {
14671       /* convert milliseconds to tenths, rounding up */
14672       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14673
14674       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14675       return buf;
14676     }
14677
14678     /* convert milliseconds to seconds, rounding up */
14679     /* use floating point to avoid strangeness of integer division
14680        with negative dividends on many machines */
14681     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14682
14683     if (second < 0) {
14684         sign = "-";
14685         second = -second;
14686     }
14687
14688     day = second / (60 * 60 * 24);
14689     second = second % (60 * 60 * 24);
14690     hour = second / (60 * 60);
14691     second = second % (60 * 60);
14692     minute = second / 60;
14693     second = second % 60;
14694
14695     if (day > 0)
14696       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14697               sign, day, hour, minute, second);
14698     else if (hour > 0)
14699       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14700     else
14701       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14702
14703     return buf;
14704 }
14705
14706
14707 /*
14708  * This is necessary because some C libraries aren't ANSI C compliant yet.
14709  */
14710 char *
14711 StrStr(string, match)
14712      char *string, *match;
14713 {
14714     int i, length;
14715
14716     length = strlen(match);
14717
14718     for (i = strlen(string) - length; i >= 0; i--, string++)
14719       if (!strncmp(match, string, length))
14720         return string;
14721
14722     return NULL;
14723 }
14724
14725 char *
14726 StrCaseStr(string, match)
14727      char *string, *match;
14728 {
14729     int i, j, length;
14730
14731     length = strlen(match);
14732
14733     for (i = strlen(string) - length; i >= 0; i--, string++) {
14734         for (j = 0; j < length; j++) {
14735             if (ToLower(match[j]) != ToLower(string[j]))
14736               break;
14737         }
14738         if (j == length) return string;
14739     }
14740
14741     return NULL;
14742 }
14743
14744 #ifndef _amigados
14745 int
14746 StrCaseCmp(s1, s2)
14747      char *s1, *s2;
14748 {
14749     char c1, c2;
14750
14751     for (;;) {
14752         c1 = ToLower(*s1++);
14753         c2 = ToLower(*s2++);
14754         if (c1 > c2) return 1;
14755         if (c1 < c2) return -1;
14756         if (c1 == NULLCHAR) return 0;
14757     }
14758 }
14759
14760
14761 int
14762 ToLower(c)
14763      int c;
14764 {
14765     return isupper(c) ? tolower(c) : c;
14766 }
14767
14768
14769 int
14770 ToUpper(c)
14771      int c;
14772 {
14773     return islower(c) ? toupper(c) : c;
14774 }
14775 #endif /* !_amigados    */
14776
14777 char *
14778 StrSave(s)
14779      char *s;
14780 {
14781   char *ret;
14782
14783   if ((ret = (char *) malloc(strlen(s) + 1)))
14784     {
14785       safeStrCpy(ret, s, strlen(s)+1);
14786     }
14787   return ret;
14788 }
14789
14790 char *
14791 StrSavePtr(s, savePtr)
14792      char *s, **savePtr;
14793 {
14794     if (*savePtr) {
14795         free(*savePtr);
14796     }
14797     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14798       safeStrCpy(*savePtr, s, strlen(s)+1);
14799     }
14800     return(*savePtr);
14801 }
14802
14803 char *
14804 PGNDate()
14805 {
14806     time_t clock;
14807     struct tm *tm;
14808     char buf[MSG_SIZ];
14809
14810     clock = time((time_t *)NULL);
14811     tm = localtime(&clock);
14812     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14813             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14814     return StrSave(buf);
14815 }
14816
14817
14818 char *
14819 PositionToFEN(move, overrideCastling)
14820      int move;
14821      char *overrideCastling;
14822 {
14823     int i, j, fromX, fromY, toX, toY;
14824     int whiteToPlay;
14825     char buf[128];
14826     char *p, *q;
14827     int emptycount;
14828     ChessSquare piece;
14829
14830     whiteToPlay = (gameMode == EditPosition) ?
14831       !blackPlaysFirst : (move % 2 == 0);
14832     p = buf;
14833
14834     /* Piece placement data */
14835     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14836         emptycount = 0;
14837         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14838             if (boards[move][i][j] == EmptySquare) {
14839                 emptycount++;
14840             } else { ChessSquare piece = boards[move][i][j];
14841                 if (emptycount > 0) {
14842                     if(emptycount<10) /* [HGM] can be >= 10 */
14843                         *p++ = '0' + emptycount;
14844                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14845                     emptycount = 0;
14846                 }
14847                 if(PieceToChar(piece) == '+') {
14848                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14849                     *p++ = '+';
14850                     piece = (ChessSquare)(DEMOTED piece);
14851                 }
14852                 *p++ = PieceToChar(piece);
14853                 if(p[-1] == '~') {
14854                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14855                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14856                     *p++ = '~';
14857                 }
14858             }
14859         }
14860         if (emptycount > 0) {
14861             if(emptycount<10) /* [HGM] can be >= 10 */
14862                 *p++ = '0' + emptycount;
14863             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14864             emptycount = 0;
14865         }
14866         *p++ = '/';
14867     }
14868     *(p - 1) = ' ';
14869
14870     /* [HGM] print Crazyhouse or Shogi holdings */
14871     if( gameInfo.holdingsWidth ) {
14872         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14873         q = p;
14874         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14875             piece = boards[move][i][BOARD_WIDTH-1];
14876             if( piece != EmptySquare )
14877               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14878                   *p++ = PieceToChar(piece);
14879         }
14880         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14881             piece = boards[move][BOARD_HEIGHT-i-1][0];
14882             if( piece != EmptySquare )
14883               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14884                   *p++ = PieceToChar(piece);
14885         }
14886
14887         if( q == p ) *p++ = '-';
14888         *p++ = ']';
14889         *p++ = ' ';
14890     }
14891
14892     /* Active color */
14893     *p++ = whiteToPlay ? 'w' : 'b';
14894     *p++ = ' ';
14895
14896   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14897     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14898   } else {
14899   if(nrCastlingRights) {
14900      q = p;
14901      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14902        /* [HGM] write directly from rights */
14903            if(boards[move][CASTLING][2] != NoRights &&
14904               boards[move][CASTLING][0] != NoRights   )
14905                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14906            if(boards[move][CASTLING][2] != NoRights &&
14907               boards[move][CASTLING][1] != NoRights   )
14908                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14909            if(boards[move][CASTLING][5] != NoRights &&
14910               boards[move][CASTLING][3] != NoRights   )
14911                 *p++ = boards[move][CASTLING][3] + AAA;
14912            if(boards[move][CASTLING][5] != NoRights &&
14913               boards[move][CASTLING][4] != NoRights   )
14914                 *p++ = boards[move][CASTLING][4] + AAA;
14915      } else {
14916
14917         /* [HGM] write true castling rights */
14918         if( nrCastlingRights == 6 ) {
14919             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14920                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14921             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14922                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14923             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14924                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14925             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14926                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14927         }
14928      }
14929      if (q == p) *p++ = '-'; /* No castling rights */
14930      *p++ = ' ';
14931   }
14932
14933   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14934      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14935     /* En passant target square */
14936     if (move > backwardMostMove) {
14937         fromX = moveList[move - 1][0] - AAA;
14938         fromY = moveList[move - 1][1] - ONE;
14939         toX = moveList[move - 1][2] - AAA;
14940         toY = moveList[move - 1][3] - ONE;
14941         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14942             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14943             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14944             fromX == toX) {
14945             /* 2-square pawn move just happened */
14946             *p++ = toX + AAA;
14947             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14948         } else {
14949             *p++ = '-';
14950         }
14951     } else if(move == backwardMostMove) {
14952         // [HGM] perhaps we should always do it like this, and forget the above?
14953         if((signed char)boards[move][EP_STATUS] >= 0) {
14954             *p++ = boards[move][EP_STATUS] + AAA;
14955             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14956         } else {
14957             *p++ = '-';
14958         }
14959     } else {
14960         *p++ = '-';
14961     }
14962     *p++ = ' ';
14963   }
14964   }
14965
14966     /* [HGM] find reversible plies */
14967     {   int i = 0, j=move;
14968
14969         if (appData.debugMode) { int k;
14970             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14971             for(k=backwardMostMove; k<=forwardMostMove; k++)
14972                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14973
14974         }
14975
14976         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14977         if( j == backwardMostMove ) i += initialRulePlies;
14978         sprintf(p, "%d ", i);
14979         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14980     }
14981     /* Fullmove number */
14982     sprintf(p, "%d", (move / 2) + 1);
14983
14984     return StrSave(buf);
14985 }
14986
14987 Boolean
14988 ParseFEN(board, blackPlaysFirst, fen)
14989     Board board;
14990      int *blackPlaysFirst;
14991      char *fen;
14992 {
14993     int i, j;
14994     char *p, c;
14995     int emptycount;
14996     ChessSquare piece;
14997
14998     p = fen;
14999
15000     /* [HGM] by default clear Crazyhouse holdings, if present */
15001     if(gameInfo.holdingsWidth) {
15002        for(i=0; i<BOARD_HEIGHT; i++) {
15003            board[i][0]             = EmptySquare; /* black holdings */
15004            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15005            board[i][1]             = (ChessSquare) 0; /* black counts */
15006            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15007        }
15008     }
15009
15010     /* Piece placement data */
15011     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15012         j = 0;
15013         for (;;) {
15014             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15015                 if (*p == '/') p++;
15016                 emptycount = gameInfo.boardWidth - j;
15017                 while (emptycount--)
15018                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15019                 break;
15020 #if(BOARD_FILES >= 10)
15021             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15022                 p++; emptycount=10;
15023                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15024                 while (emptycount--)
15025                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15026 #endif
15027             } else if (isdigit(*p)) {
15028                 emptycount = *p++ - '0';
15029                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15030                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15031                 while (emptycount--)
15032                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15033             } else if (*p == '+' || isalpha(*p)) {
15034                 if (j >= gameInfo.boardWidth) return FALSE;
15035                 if(*p=='+') {
15036                     piece = CharToPiece(*++p);
15037                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15038                     piece = (ChessSquare) (PROMOTED piece ); p++;
15039                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15040                 } else piece = CharToPiece(*p++);
15041
15042                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15043                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15044                     piece = (ChessSquare) (PROMOTED piece);
15045                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15046                     p++;
15047                 }
15048                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15049             } else {
15050                 return FALSE;
15051             }
15052         }
15053     }
15054     while (*p == '/' || *p == ' ') p++;
15055
15056     /* [HGM] look for Crazyhouse holdings here */
15057     while(*p==' ') p++;
15058     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15059         if(*p == '[') p++;
15060         if(*p == '-' ) p++; /* empty holdings */ else {
15061             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15062             /* if we would allow FEN reading to set board size, we would   */
15063             /* have to add holdings and shift the board read so far here   */
15064             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15065                 p++;
15066                 if((int) piece >= (int) BlackPawn ) {
15067                     i = (int)piece - (int)BlackPawn;
15068                     i = PieceToNumber((ChessSquare)i);
15069                     if( i >= gameInfo.holdingsSize ) return FALSE;
15070                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15071                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15072                 } else {
15073                     i = (int)piece - (int)WhitePawn;
15074                     i = PieceToNumber((ChessSquare)i);
15075                     if( i >= gameInfo.holdingsSize ) return FALSE;
15076                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15077                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15078                 }
15079             }
15080         }
15081         if(*p == ']') p++;
15082     }
15083
15084     while(*p == ' ') p++;
15085
15086     /* Active color */
15087     c = *p++;
15088     if(appData.colorNickNames) {
15089       if( c == appData.colorNickNames[0] ) c = 'w'; else
15090       if( c == appData.colorNickNames[1] ) c = 'b';
15091     }
15092     switch (c) {
15093       case 'w':
15094         *blackPlaysFirst = FALSE;
15095         break;
15096       case 'b':
15097         *blackPlaysFirst = TRUE;
15098         break;
15099       default:
15100         return FALSE;
15101     }
15102
15103     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15104     /* return the extra info in global variiables             */
15105
15106     /* set defaults in case FEN is incomplete */
15107     board[EP_STATUS] = EP_UNKNOWN;
15108     for(i=0; i<nrCastlingRights; i++ ) {
15109         board[CASTLING][i] =
15110             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15111     }   /* assume possible unless obviously impossible */
15112     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15113     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15114     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15115                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15116     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15117     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15118     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15119                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15120     FENrulePlies = 0;
15121
15122     while(*p==' ') p++;
15123     if(nrCastlingRights) {
15124       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15125           /* castling indicator present, so default becomes no castlings */
15126           for(i=0; i<nrCastlingRights; i++ ) {
15127                  board[CASTLING][i] = NoRights;
15128           }
15129       }
15130       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15131              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15132              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15133              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15134         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15135
15136         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15137             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15138             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15139         }
15140         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15141             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15142         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15143                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15144         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15145                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15146         switch(c) {
15147           case'K':
15148               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15149               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15150               board[CASTLING][2] = whiteKingFile;
15151               break;
15152           case'Q':
15153               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15154               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15155               board[CASTLING][2] = whiteKingFile;
15156               break;
15157           case'k':
15158               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15159               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15160               board[CASTLING][5] = blackKingFile;
15161               break;
15162           case'q':
15163               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15164               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15165               board[CASTLING][5] = blackKingFile;
15166           case '-':
15167               break;
15168           default: /* FRC castlings */
15169               if(c >= 'a') { /* black rights */
15170                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15171                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15172                   if(i == BOARD_RGHT) break;
15173                   board[CASTLING][5] = i;
15174                   c -= AAA;
15175                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15176                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15177                   if(c > i)
15178                       board[CASTLING][3] = c;
15179                   else
15180                       board[CASTLING][4] = c;
15181               } else { /* white rights */
15182                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15183                     if(board[0][i] == WhiteKing) break;
15184                   if(i == BOARD_RGHT) break;
15185                   board[CASTLING][2] = i;
15186                   c -= AAA - 'a' + 'A';
15187                   if(board[0][c] >= WhiteKing) break;
15188                   if(c > i)
15189                       board[CASTLING][0] = c;
15190                   else
15191                       board[CASTLING][1] = c;
15192               }
15193         }
15194       }
15195       for(i=0; i<nrCastlingRights; i++)
15196         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15197     if (appData.debugMode) {
15198         fprintf(debugFP, "FEN castling rights:");
15199         for(i=0; i<nrCastlingRights; i++)
15200         fprintf(debugFP, " %d", board[CASTLING][i]);
15201         fprintf(debugFP, "\n");
15202     }
15203
15204       while(*p==' ') p++;
15205     }
15206
15207     /* read e.p. field in games that know e.p. capture */
15208     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15209        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15210       if(*p=='-') {
15211         p++; board[EP_STATUS] = EP_NONE;
15212       } else {
15213          char c = *p++ - AAA;
15214
15215          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15216          if(*p >= '0' && *p <='9') p++;
15217          board[EP_STATUS] = c;
15218       }
15219     }
15220
15221
15222     if(sscanf(p, "%d", &i) == 1) {
15223         FENrulePlies = i; /* 50-move ply counter */
15224         /* (The move number is still ignored)    */
15225     }
15226
15227     return TRUE;
15228 }
15229
15230 void
15231 EditPositionPasteFEN(char *fen)
15232 {
15233   if (fen != NULL) {
15234     Board initial_position;
15235
15236     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15237       DisplayError(_("Bad FEN position in clipboard"), 0);
15238       return ;
15239     } else {
15240       int savedBlackPlaysFirst = blackPlaysFirst;
15241       EditPositionEvent();
15242       blackPlaysFirst = savedBlackPlaysFirst;
15243       CopyBoard(boards[0], initial_position);
15244       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15245       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15246       DisplayBothClocks();
15247       DrawPosition(FALSE, boards[currentMove]);
15248     }
15249   }
15250 }
15251
15252 static char cseq[12] = "\\   ";
15253
15254 Boolean set_cont_sequence(char *new_seq)
15255 {
15256     int len;
15257     Boolean ret;
15258
15259     // handle bad attempts to set the sequence
15260         if (!new_seq)
15261                 return 0; // acceptable error - no debug
15262
15263     len = strlen(new_seq);
15264     ret = (len > 0) && (len < sizeof(cseq));
15265     if (ret)
15266       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15267     else if (appData.debugMode)
15268       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15269     return ret;
15270 }
15271
15272 /*
15273     reformat a source message so words don't cross the width boundary.  internal
15274     newlines are not removed.  returns the wrapped size (no null character unless
15275     included in source message).  If dest is NULL, only calculate the size required
15276     for the dest buffer.  lp argument indicats line position upon entry, and it's
15277     passed back upon exit.
15278 */
15279 int wrap(char *dest, char *src, int count, int width, int *lp)
15280 {
15281     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15282
15283     cseq_len = strlen(cseq);
15284     old_line = line = *lp;
15285     ansi = len = clen = 0;
15286
15287     for (i=0; i < count; i++)
15288     {
15289         if (src[i] == '\033')
15290             ansi = 1;
15291
15292         // if we hit the width, back up
15293         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15294         {
15295             // store i & len in case the word is too long
15296             old_i = i, old_len = len;
15297
15298             // find the end of the last word
15299             while (i && src[i] != ' ' && src[i] != '\n')
15300             {
15301                 i--;
15302                 len--;
15303             }
15304
15305             // word too long?  restore i & len before splitting it
15306             if ((old_i-i+clen) >= width)
15307             {
15308                 i = old_i;
15309                 len = old_len;
15310             }
15311
15312             // extra space?
15313             if (i && src[i-1] == ' ')
15314                 len--;
15315
15316             if (src[i] != ' ' && src[i] != '\n')
15317             {
15318                 i--;
15319                 if (len)
15320                     len--;
15321             }
15322
15323             // now append the newline and continuation sequence
15324             if (dest)
15325                 dest[len] = '\n';
15326             len++;
15327             if (dest)
15328                 strncpy(dest+len, cseq, cseq_len);
15329             len += cseq_len;
15330             line = cseq_len;
15331             clen = cseq_len;
15332             continue;
15333         }
15334
15335         if (dest)
15336             dest[len] = src[i];
15337         len++;
15338         if (!ansi)
15339             line++;
15340         if (src[i] == '\n')
15341             line = 0;
15342         if (src[i] == 'm')
15343             ansi = 0;
15344     }
15345     if (dest && appData.debugMode)
15346     {
15347         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15348             count, width, line, len, *lp);
15349         show_bytes(debugFP, src, count);
15350         fprintf(debugFP, "\ndest: ");
15351         show_bytes(debugFP, dest, len);
15352         fprintf(debugFP, "\n");
15353     }
15354     *lp = dest ? line : old_line;
15355
15356     return len;
15357 }
15358
15359 // [HGM] vari: routines for shelving variations
15360
15361 void
15362 PushTail(int firstMove, int lastMove)
15363 {
15364         int i, j, nrMoves = lastMove - firstMove;
15365
15366         if(appData.icsActive) { // only in local mode
15367                 forwardMostMove = currentMove; // mimic old ICS behavior
15368                 return;
15369         }
15370         if(storedGames >= MAX_VARIATIONS-1) return;
15371
15372         // push current tail of game on stack
15373         savedResult[storedGames] = gameInfo.result;
15374         savedDetails[storedGames] = gameInfo.resultDetails;
15375         gameInfo.resultDetails = NULL;
15376         savedFirst[storedGames] = firstMove;
15377         savedLast [storedGames] = lastMove;
15378         savedFramePtr[storedGames] = framePtr;
15379         framePtr -= nrMoves; // reserve space for the boards
15380         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15381             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15382             for(j=0; j<MOVE_LEN; j++)
15383                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15384             for(j=0; j<2*MOVE_LEN; j++)
15385                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15386             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15387             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15388             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15389             pvInfoList[firstMove+i-1].depth = 0;
15390             commentList[framePtr+i] = commentList[firstMove+i];
15391             commentList[firstMove+i] = NULL;
15392         }
15393
15394         storedGames++;
15395         forwardMostMove = firstMove; // truncate game so we can start variation
15396         if(storedGames == 1) GreyRevert(FALSE);
15397 }
15398
15399 Boolean
15400 PopTail(Boolean annotate)
15401 {
15402         int i, j, nrMoves;
15403         char buf[8000], moveBuf[20];
15404
15405         if(appData.icsActive) return FALSE; // only in local mode
15406         if(!storedGames) return FALSE; // sanity
15407         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15408
15409         storedGames--;
15410         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15411         nrMoves = savedLast[storedGames] - currentMove;
15412         if(annotate) {
15413                 int cnt = 10;
15414                 if(!WhiteOnMove(currentMove))
15415                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15416                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15417                 for(i=currentMove; i<forwardMostMove; i++) {
15418                         if(WhiteOnMove(i))
15419                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15420                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15421                         strcat(buf, moveBuf);
15422                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15423                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15424                 }
15425                 strcat(buf, ")");
15426         }
15427         for(i=1; i<=nrMoves; i++) { // copy last variation back
15428             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15429             for(j=0; j<MOVE_LEN; j++)
15430                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15431             for(j=0; j<2*MOVE_LEN; j++)
15432                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15433             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15434             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15435             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15436             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15437             commentList[currentMove+i] = commentList[framePtr+i];
15438             commentList[framePtr+i] = NULL;
15439         }
15440         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15441         framePtr = savedFramePtr[storedGames];
15442         gameInfo.result = savedResult[storedGames];
15443         if(gameInfo.resultDetails != NULL) {
15444             free(gameInfo.resultDetails);
15445       }
15446         gameInfo.resultDetails = savedDetails[storedGames];
15447         forwardMostMove = currentMove + nrMoves;
15448         if(storedGames == 0) GreyRevert(TRUE);
15449         return TRUE;
15450 }
15451
15452 void
15453 CleanupTail()
15454 {       // remove all shelved variations
15455         int i;
15456         for(i=0; i<storedGames; i++) {
15457             if(savedDetails[i])
15458                 free(savedDetails[i]);
15459             savedDetails[i] = NULL;
15460         }
15461         for(i=framePtr; i<MAX_MOVES; i++) {
15462                 if(commentList[i]) free(commentList[i]);
15463                 commentList[i] = NULL;
15464         }
15465         framePtr = MAX_MOVES-1;
15466         storedGames = 0;
15467 }
15468
15469 void
15470 LoadVariation(int index, char *text)
15471 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15472         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15473         int level = 0, move;
15474
15475         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15476         // first find outermost bracketing variation
15477         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15478             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15479                 if(*p == '{') wait = '}'; else
15480                 if(*p == '[') wait = ']'; else
15481                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15482                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15483             }
15484             if(*p == wait) wait = NULLCHAR; // closing ]} found
15485             p++;
15486         }
15487         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15488         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15489         end[1] = NULLCHAR; // clip off comment beyond variation
15490         ToNrEvent(currentMove-1);
15491         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15492         // kludge: use ParsePV() to append variation to game
15493         move = currentMove;
15494         ParsePV(start, TRUE);
15495         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15496         ClearPremoveHighlights();
15497         CommentPopDown();
15498         ToNrEvent(currentMove+1);
15499 }
15500