52240181daaa042588216ab90eb33ed235e82017
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 { // [HGM] made safe
315   int i;
316   assert( dst != NULL );
317   assert( src != NULL );
318   assert( count > 0 );
319
320   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
321   if(  i == count && dst[count-1] != NULLCHAR)
322     {
323       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
324       if(appData.debugMode)
325       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
326     }
327
328   return dst;
329 }
330
331 /* Some compiler can't cast u64 to double
332  * This function do the job for us:
333
334  * We use the highest bit for cast, this only
335  * works if the highest bit is not
336  * in use (This should not happen)
337  *
338  * We used this for all compiler
339  */
340 double
341 u64ToDouble(u64 value)
342 {
343   double r;
344   u64 tmp = value & u64Const(0x7fffffffffffffff);
345   r = (double)(s64)tmp;
346   if (value & u64Const(0x8000000000000000))
347        r +=  9.2233720368547758080e18; /* 2^63 */
348  return r;
349 }
350
351 /* Fake up flags for now, as we aren't keeping track of castling
352    availability yet. [HGM] Change of logic: the flag now only
353    indicates the type of castlings allowed by the rule of the game.
354    The actual rights themselves are maintained in the array
355    castlingRights, as part of the game history, and are not probed
356    by this function.
357  */
358 int
359 PosFlags(index)
360 {
361   int flags = F_ALL_CASTLE_OK;
362   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
363   switch (gameInfo.variant) {
364   case VariantSuicide:
365     flags &= ~F_ALL_CASTLE_OK;
366   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
367     flags |= F_IGNORE_CHECK;
368   case VariantLosers:
369     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
370     break;
371   case VariantAtomic:
372     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
373     break;
374   case VariantKriegspiel:
375     flags |= F_KRIEGSPIEL_CAPTURE;
376     break;
377   case VariantCapaRandom:
378   case VariantFischeRandom:
379     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
380   case VariantNoCastle:
381   case VariantShatranj:
382   case VariantCourier:
383   case VariantMakruk:
384     flags &= ~F_ALL_CASTLE_OK;
385     break;
386   default:
387     break;
388   }
389   return flags;
390 }
391
392 FILE *gameFileFP, *debugFP;
393
394 /*
395     [AS] Note: sometimes, the sscanf() function is used to parse the input
396     into a fixed-size buffer. Because of this, we must be prepared to
397     receive strings as long as the size of the input buffer, which is currently
398     set to 4K for Windows and 8K for the rest.
399     So, we must either allocate sufficiently large buffers here, or
400     reduce the size of the input buffer in the input reading part.
401 */
402
403 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
404 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
405 char thinkOutput1[MSG_SIZ*10];
406
407 ChessProgramState first, second;
408
409 /* premove variables */
410 int premoveToX = 0;
411 int premoveToY = 0;
412 int premoveFromX = 0;
413 int premoveFromY = 0;
414 int premovePromoChar = 0;
415 int gotPremove = 0;
416 Boolean alarmSounded;
417 /* end premove variables */
418
419 char *ics_prefix = "$";
420 int ics_type = ICS_GENERIC;
421
422 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
423 int pauseExamForwardMostMove = 0;
424 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
425 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
426 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
427 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
428 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
429 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
430 int whiteFlag = FALSE, blackFlag = FALSE;
431 int userOfferedDraw = FALSE;
432 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
433 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
434 int cmailMoveType[CMAIL_MAX_GAMES];
435 long ics_clock_paused = 0;
436 ProcRef icsPR = NoProc, cmailPR = NoProc;
437 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
438 GameMode gameMode = BeginningOfGame;
439 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
440 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
441 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
442 int hiddenThinkOutputState = 0; /* [AS] */
443 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
444 int adjudicateLossPlies = 6;
445 char white_holding[64], black_holding[64];
446 TimeMark lastNodeCountTime;
447 long lastNodeCount=0;
448 int shiftKey; // [HGM] set by mouse handler
449
450 int have_sent_ICS_logon = 0;
451 int movesPerSession;
452 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
453 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
454 long timeControl_2; /* [AS] Allow separate time controls */
455 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
456 long timeRemaining[2][MAX_MOVES];
457 int matchGame = 0;
458 TimeMark programStartTime;
459 char ics_handle[MSG_SIZ];
460 int have_set_title = 0;
461
462 /* animateTraining preserves the state of appData.animate
463  * when Training mode is activated. This allows the
464  * response to be animated when appData.animate == TRUE and
465  * appData.animateDragging == TRUE.
466  */
467 Boolean animateTraining;
468
469 GameInfo gameInfo;
470
471 AppData appData;
472
473 Board boards[MAX_MOVES];
474 /* [HGM] Following 7 needed for accurate legality tests: */
475 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
476 signed char  initialRights[BOARD_FILES];
477 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
478 int   initialRulePlies, FENrulePlies;
479 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
480 int loadFlag = 0;
481 int shuffleOpenings;
482 int mute; // mute all sounds
483
484 // [HGM] vari: next 12 to save and restore variations
485 #define MAX_VARIATIONS 10
486 int framePtr = MAX_MOVES-1; // points to free stack entry
487 int storedGames = 0;
488 int savedFirst[MAX_VARIATIONS];
489 int savedLast[MAX_VARIATIONS];
490 int savedFramePtr[MAX_VARIATIONS];
491 char *savedDetails[MAX_VARIATIONS];
492 ChessMove savedResult[MAX_VARIATIONS];
493
494 void PushTail P((int firstMove, int lastMove));
495 Boolean PopTail P((Boolean annotate));
496 void CleanupTail P((void));
497
498 ChessSquare  FIDEArray[2][BOARD_FILES] = {
499     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
500         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
501     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
502         BlackKing, BlackBishop, BlackKnight, BlackRook }
503 };
504
505 ChessSquare twoKingsArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackKing, BlackKnight, BlackRook }
510 };
511
512 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
514         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
515     { BlackRook, BlackMan, BlackBishop, BlackQueen,
516         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
517 };
518
519 ChessSquare SpartanArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
521         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
522     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
523         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
524 };
525
526 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
530         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
531 };
532
533 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
535         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
537         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 };
539
540 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
542         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackMan, BlackFerz,
544         BlackKing, BlackMan, BlackKnight, BlackRook }
545 };
546
547
548 #if (BOARD_FILES>=10)
549 ChessSquare ShogiArray[2][BOARD_FILES] = {
550     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
551         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
552     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
553         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
554 };
555
556 ChessSquare XiangqiArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
558         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
560         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
561 };
562
563 ChessSquare CapablancaArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
567         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
568 };
569
570 ChessSquare GreatArray[2][BOARD_FILES] = {
571     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
572         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
573     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
574         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
575 };
576
577 ChessSquare JanusArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
579         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
580     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
581         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
582 };
583
584 #ifdef GOTHIC
585 ChessSquare GothicArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
587         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
589         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
590 };
591 #else // !GOTHIC
592 #define GothicArray CapablancaArray
593 #endif // !GOTHIC
594
595 #ifdef FALCON
596 ChessSquare FalconArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
598         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
600         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
601 };
602 #else // !FALCON
603 #define FalconArray CapablancaArray
604 #endif // !FALCON
605
606 #else // !(BOARD_FILES>=10)
607 #define XiangqiPosition FIDEArray
608 #define CapablancaArray FIDEArray
609 #define GothicArray FIDEArray
610 #define GreatArray FIDEArray
611 #endif // !(BOARD_FILES>=10)
612
613 #if (BOARD_FILES>=12)
614 ChessSquare CourierArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
616         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
618         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
619 };
620 #else // !(BOARD_FILES>=12)
621 #define CourierArray CapablancaArray
622 #endif // !(BOARD_FILES>=12)
623
624
625 Board initialPosition;
626
627
628 /* Convert str to a rating. Checks for special cases of "----",
629
630    "++++", etc. Also strips ()'s */
631 int
632 string_to_rating(str)
633   char *str;
634 {
635   while(*str && !isdigit(*str)) ++str;
636   if (!*str)
637     return 0;   /* One of the special "no rating" cases */
638   else
639     return atoi(str);
640 }
641
642 void
643 ClearProgramStats()
644 {
645     /* Init programStats */
646     programStats.movelist[0] = 0;
647     programStats.depth = 0;
648     programStats.nr_moves = 0;
649     programStats.moves_left = 0;
650     programStats.nodes = 0;
651     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
652     programStats.score = 0;
653     programStats.got_only_move = 0;
654     programStats.got_fail = 0;
655     programStats.line_is_book = 0;
656 }
657
658 void
659 InitBackEnd1()
660 {
661     int matched, min, sec;
662
663     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
664     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
665
666     GetTimeMark(&programStartTime);
667     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
668
669     ClearProgramStats();
670     programStats.ok_to_send = 1;
671     programStats.seen_stat = 0;
672
673     /*
674      * Initialize game list
675      */
676     ListNew(&gameList);
677
678
679     /*
680      * Internet chess server status
681      */
682     if (appData.icsActive) {
683         appData.matchMode = FALSE;
684         appData.matchGames = 0;
685 #if ZIPPY
686         appData.noChessProgram = !appData.zippyPlay;
687 #else
688         appData.zippyPlay = FALSE;
689         appData.zippyTalk = FALSE;
690         appData.noChessProgram = TRUE;
691 #endif
692         if (*appData.icsHelper != NULLCHAR) {
693             appData.useTelnet = TRUE;
694             appData.telnetProgram = appData.icsHelper;
695         }
696     } else {
697         appData.zippyTalk = appData.zippyPlay = FALSE;
698     }
699
700     /* [AS] Initialize pv info list [HGM] and game state */
701     {
702         int i, j;
703
704         for( i=0; i<=framePtr; i++ ) {
705             pvInfoList[i].depth = -1;
706             boards[i][EP_STATUS] = EP_NONE;
707             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
708         }
709     }
710
711     /*
712      * Parse timeControl resource
713      */
714     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
715                           appData.movesPerSession)) {
716         char buf[MSG_SIZ];
717         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
718         DisplayFatalError(buf, 0, 2);
719     }
720
721     /*
722      * Parse searchTime resource
723      */
724     if (*appData.searchTime != NULLCHAR) {
725         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
726         if (matched == 1) {
727             searchTime = min * 60;
728         } else if (matched == 2) {
729             searchTime = min * 60 + sec;
730         } else {
731             char buf[MSG_SIZ];
732             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
733             DisplayFatalError(buf, 0, 2);
734         }
735     }
736
737     /* [AS] Adjudication threshold */
738     adjudicateLossThreshold = appData.adjudicateLossThreshold;
739
740     first.which = _("first");
741     second.which = _("second");
742     first.maybeThinking = second.maybeThinking = FALSE;
743     first.pr = second.pr = NoProc;
744     first.isr = second.isr = NULL;
745     first.sendTime = second.sendTime = 2;
746     first.sendDrawOffers = 1;
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754     first.program = appData.firstChessProgram;
755     second.program = appData.secondChessProgram;
756     first.host = appData.firstHost;
757     second.host = appData.secondHost;
758     first.dir = appData.firstDirectory;
759     second.dir = appData.secondDirectory;
760     first.other = &second;
761     second.other = &first;
762     first.initString = appData.initString;
763     second.initString = appData.secondInitString;
764     first.computerString = appData.firstComputerString;
765     second.computerString = appData.secondComputerString;
766     first.useSigint = second.useSigint = TRUE;
767     first.useSigterm = second.useSigterm = TRUE;
768     first.reuse = appData.reuseFirst;
769     second.reuse = appData.reuseSecond;
770     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
771     second.nps = appData.secondNPS;
772     first.useSetboard = second.useSetboard = FALSE;
773     first.useSAN = second.useSAN = FALSE;
774     first.usePing = second.usePing = FALSE;
775     first.lastPing = second.lastPing = 0;
776     first.lastPong = second.lastPong = 0;
777     first.usePlayother = second.usePlayother = FALSE;
778     first.useColors = second.useColors = TRUE;
779     first.useUsermove = second.useUsermove = FALSE;
780     first.sendICS = second.sendICS = FALSE;
781     first.sendName = second.sendName = appData.icsActive;
782     first.sdKludge = second.sdKludge = FALSE;
783     first.stKludge = second.stKludge = FALSE;
784     TidyProgramName(first.program, first.host, first.tidy);
785     TidyProgramName(second.program, second.host, second.tidy);
786     first.matchWins = second.matchWins = 0;
787     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
788     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
789     first.analysisSupport = second.analysisSupport = 2; /* detect */
790     first.analyzing = second.analyzing = FALSE;
791     first.initDone = second.initDone = FALSE;
792
793     /* New features added by Tord: */
794     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
795     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
796     /* End of new features added by Tord. */
797     first.fenOverride  = appData.fenOverride1;
798     second.fenOverride = appData.fenOverride2;
799
800     /* [HGM] time odds: set factor for each machine */
801     first.timeOdds  = appData.firstTimeOdds;
802     second.timeOdds = appData.secondTimeOdds;
803     { float norm = 1;
804         if(appData.timeOddsMode) {
805             norm = first.timeOdds;
806             if(norm > second.timeOdds) norm = second.timeOdds;
807         }
808         first.timeOdds /= norm;
809         second.timeOdds /= norm;
810     }
811
812     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
813     first.accumulateTC = appData.firstAccumulateTC;
814     second.accumulateTC = appData.secondAccumulateTC;
815     first.maxNrOfSessions = second.maxNrOfSessions = 1;
816
817     /* [HGM] debug */
818     first.debug = second.debug = FALSE;
819     first.supportsNPS = second.supportsNPS = UNKNOWN;
820
821     /* [HGM] options */
822     first.optionSettings  = appData.firstOptions;
823     second.optionSettings = appData.secondOptions;
824
825     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
826     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
827     first.isUCI = appData.firstIsUCI; /* [AS] */
828     second.isUCI = appData.secondIsUCI; /* [AS] */
829     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
830     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
831
832     if (appData.firstProtocolVersion > PROTOVER
833         || appData.firstProtocolVersion < 1)
834       {
835         char buf[MSG_SIZ];
836         int len;
837
838         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
839                        appData.firstProtocolVersion);
840         if( (len > MSG_SIZ) && appData.debugMode )
841           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842
843         DisplayFatalError(buf, 0, 2);
844       }
845     else
846       {
847         first.protocolVersion = appData.firstProtocolVersion;
848       }
849
850     if (appData.secondProtocolVersion > PROTOVER
851         || appData.secondProtocolVersion < 1)
852       {
853         char buf[MSG_SIZ];
854         int len;
855
856         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
857                        appData.secondProtocolVersion);
858         if( (len > MSG_SIZ) && appData.debugMode )
859           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
860
861         DisplayFatalError(buf, 0, 2);
862       }
863     else
864       {
865         second.protocolVersion = appData.secondProtocolVersion;
866       }
867
868     if (appData.icsActive) {
869         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
870 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
871     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
872         appData.clockMode = FALSE;
873         first.sendTime = second.sendTime = 0;
874     }
875
876 #if ZIPPY
877     /* Override some settings from environment variables, for backward
878        compatibility.  Unfortunately it's not feasible to have the env
879        vars just set defaults, at least in xboard.  Ugh.
880     */
881     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
882       ZippyInit();
883     }
884 #endif
885
886     if (appData.noChessProgram) {
887         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
888         sprintf(programVersion, "%s", PACKAGE_STRING);
889     } else {
890       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
891       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
892       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
893     }
894
895     if (!appData.icsActive) {
896       char buf[MSG_SIZ];
897       int len;
898
899       /* Check for variants that are supported only in ICS mode,
900          or not at all.  Some that are accepted here nevertheless
901          have bugs; see comments below.
902       */
903       VariantClass variant = StringToVariant(appData.variant);
904       switch (variant) {
905       case VariantBughouse:     /* need four players and two boards */
906       case VariantKriegspiel:   /* need to hide pieces and move details */
907         /* case VariantFischeRandom: (Fabien: moved below) */
908         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
909         if( (len > MSG_SIZ) && appData.debugMode )
910           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
911
912         DisplayFatalError(buf, 0, 2);
913         return;
914
915       case VariantUnknown:
916       case VariantLoadable:
917       case Variant29:
918       case Variant30:
919       case Variant31:
920       case Variant32:
921       case Variant33:
922       case Variant34:
923       case Variant35:
924       case Variant36:
925       default:
926         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
927         if( (len > MSG_SIZ) && appData.debugMode )
928           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
929
930         DisplayFatalError(buf, 0, 2);
931         return;
932
933       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
934       case VariantFairy:      /* [HGM] TestLegality definitely off! */
935       case VariantGothic:     /* [HGM] should work */
936       case VariantCapablanca: /* [HGM] should work */
937       case VariantCourier:    /* [HGM] initial forced moves not implemented */
938       case VariantShogi:      /* [HGM] could still mate with pawn drop */
939       case VariantKnightmate: /* [HGM] should work */
940       case VariantCylinder:   /* [HGM] untested */
941       case VariantFalcon:     /* [HGM] untested */
942       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
943                                  offboard interposition not understood */
944       case VariantNormal:     /* definitely works! */
945       case VariantWildCastle: /* pieces not automatically shuffled */
946       case VariantNoCastle:   /* pieces not automatically shuffled */
947       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
948       case VariantLosers:     /* should work except for win condition,
949                                  and doesn't know captures are mandatory */
950       case VariantSuicide:    /* should work except for win condition,
951                                  and doesn't know captures are mandatory */
952       case VariantGiveaway:   /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantTwoKings:   /* should work */
955       case VariantAtomic:     /* should work except for win condition */
956       case Variant3Check:     /* should work except for win condition */
957       case VariantShatranj:   /* should work except for all win conditions */
958       case VariantMakruk:     /* should work except for daw countdown */
959       case VariantBerolina:   /* might work if TestLegality is off */
960       case VariantCapaRandom: /* should work */
961       case VariantJanus:      /* should work */
962       case VariantSuper:      /* experimental */
963       case VariantGreat:      /* experimental, requires legality testing to be off */
964       case VariantSChess:     /* S-Chess, should work */
965       case VariantSpartan:    /* should work */
966         break;
967       }
968     }
969
970     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
971     InitEngineUCI( installDir, &second );
972 }
973
974 int NextIntegerFromString( char ** str, long * value )
975 {
976     int result = -1;
977     char * s = *str;
978
979     while( *s == ' ' || *s == '\t' ) {
980         s++;
981     }
982
983     *value = 0;
984
985     if( *s >= '0' && *s <= '9' ) {
986         while( *s >= '0' && *s <= '9' ) {
987             *value = *value * 10 + (*s - '0');
988             s++;
989         }
990
991         result = 0;
992     }
993
994     *str = s;
995
996     return result;
997 }
998
999 int NextTimeControlFromString( char ** str, long * value )
1000 {
1001     long temp;
1002     int result = NextIntegerFromString( str, &temp );
1003
1004     if( result == 0 ) {
1005         *value = temp * 60; /* Minutes */
1006         if( **str == ':' ) {
1007             (*str)++;
1008             result = NextIntegerFromString( str, &temp );
1009             *value += temp; /* Seconds */
1010         }
1011     }
1012
1013     return result;
1014 }
1015
1016 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1017 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1018     int result = -1, type = 0; long temp, temp2;
1019
1020     if(**str != ':') return -1; // old params remain in force!
1021     (*str)++;
1022     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1023     if( NextIntegerFromString( str, &temp ) ) return -1;
1024     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1025
1026     if(**str != '/') {
1027         /* time only: incremental or sudden-death time control */
1028         if(**str == '+') { /* increment follows; read it */
1029             (*str)++;
1030             if(**str == '!') type = *(*str)++; // Bronstein TC
1031             if(result = NextIntegerFromString( str, &temp2)) return -1;
1032             *inc = temp2 * 1000;
1033             if(**str == '.') { // read fraction of increment
1034                 char *start = ++(*str);
1035                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1036                 temp2 *= 1000;
1037                 while(start++ < *str) temp2 /= 10;
1038                 *inc += temp2;
1039             }
1040         } else *inc = 0;
1041         *moves = 0; *tc = temp * 1000; *incType = type;
1042         return 0;
1043     }
1044
1045     (*str)++; /* classical time control */
1046     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1047
1048     if(result == 0) {
1049         *moves = temp;
1050         *tc    = temp2 * 1000;
1051         *inc   = 0;
1052         *incType = type;
1053     }
1054     return result;
1055 }
1056
1057 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1058 {   /* [HGM] get time to add from the multi-session time-control string */
1059     int incType, moves=1; /* kludge to force reading of first session */
1060     long time, increment;
1061     char *s = tcString;
1062
1063     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1064     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1065     do {
1066         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1067         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1068         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1069         if(movenr == -1) return time;    /* last move before new session     */
1070         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1071         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1072         if(!moves) return increment;     /* current session is incremental   */
1073         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1074     } while(movenr >= -1);               /* try again for next session       */
1075
1076     return 0; // no new time quota on this move
1077 }
1078
1079 int
1080 ParseTimeControl(tc, ti, mps)
1081      char *tc;
1082      float ti;
1083      int mps;
1084 {
1085   long tc1;
1086   long tc2;
1087   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1088   int min, sec=0;
1089
1090   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1091   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1092       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1093   if(ti > 0) {
1094
1095     if(mps)
1096       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1097     else 
1098       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1099   } else {
1100     if(mps)
1101       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1102     else 
1103       snprintf(buf, MSG_SIZ, ":%s", mytc);
1104   }
1105   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1106   
1107   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1108     return FALSE;
1109   }
1110
1111   if( *tc == '/' ) {
1112     /* Parse second time control */
1113     tc++;
1114
1115     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1116       return FALSE;
1117     }
1118
1119     if( tc2 == 0 ) {
1120       return FALSE;
1121     }
1122
1123     timeControl_2 = tc2 * 1000;
1124   }
1125   else {
1126     timeControl_2 = 0;
1127   }
1128
1129   if( tc1 == 0 ) {
1130     return FALSE;
1131   }
1132
1133   timeControl = tc1 * 1000;
1134
1135   if (ti >= 0) {
1136     timeIncrement = ti * 1000;  /* convert to ms */
1137     movesPerSession = 0;
1138   } else {
1139     timeIncrement = 0;
1140     movesPerSession = mps;
1141   }
1142   return TRUE;
1143 }
1144
1145 void
1146 InitBackEnd2()
1147 {
1148     if (appData.debugMode) {
1149         fprintf(debugFP, "%s\n", programVersion);
1150     }
1151
1152     set_cont_sequence(appData.wrapContSeq);
1153     if (appData.matchGames > 0) {
1154         appData.matchMode = TRUE;
1155     } else if (appData.matchMode) {
1156         appData.matchGames = 1;
1157     }
1158     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1159         appData.matchGames = appData.sameColorGames;
1160     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1161         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1162         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1163     }
1164     Reset(TRUE, FALSE);
1165     if (appData.noChessProgram || first.protocolVersion == 1) {
1166       InitBackEnd3();
1167     } else {
1168       /* kludge: allow timeout for initial "feature" commands */
1169       FreezeUI();
1170       DisplayMessage("", _("Starting chess program"));
1171       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1172     }
1173 }
1174
1175 void
1176 InitBackEnd3 P((void))
1177 {
1178     GameMode initialMode;
1179     char buf[MSG_SIZ];
1180     int err, len;
1181
1182     InitChessProgram(&first, startedFromSetupPosition);
1183
1184     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1185         free(programVersion);
1186         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1187         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1188     }
1189
1190     if (appData.icsActive) {
1191 #ifdef WIN32
1192         /* [DM] Make a console window if needed [HGM] merged ifs */
1193         ConsoleCreate();
1194 #endif
1195         err = establish();
1196         if (err != 0)
1197           {
1198             if (*appData.icsCommPort != NULLCHAR)
1199               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1200                              appData.icsCommPort);
1201             else
1202               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1203                         appData.icsHost, appData.icsPort);
1204
1205             if( (len > MSG_SIZ) && appData.debugMode )
1206               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1207
1208             DisplayFatalError(buf, err, 1);
1209             return;
1210         }
1211         SetICSMode();
1212         telnetISR =
1213           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1214         fromUserISR =
1215           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1216         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1217             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1218     } else if (appData.noChessProgram) {
1219         SetNCPMode();
1220     } else {
1221         SetGNUMode();
1222     }
1223
1224     if (*appData.cmailGameName != NULLCHAR) {
1225         SetCmailMode();
1226         OpenLoopback(&cmailPR);
1227         cmailISR =
1228           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1229     }
1230
1231     ThawUI();
1232     DisplayMessage("", "");
1233     if (StrCaseCmp(appData.initialMode, "") == 0) {
1234       initialMode = BeginningOfGame;
1235     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1236       initialMode = TwoMachinesPlay;
1237     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1238       initialMode = AnalyzeFile;
1239     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1240       initialMode = AnalyzeMode;
1241     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1242       initialMode = MachinePlaysWhite;
1243     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1244       initialMode = MachinePlaysBlack;
1245     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1246       initialMode = EditGame;
1247     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1248       initialMode = EditPosition;
1249     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1250       initialMode = Training;
1251     } else {
1252       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1253       if( (len > MSG_SIZ) && appData.debugMode )
1254         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1255
1256       DisplayFatalError(buf, 0, 2);
1257       return;
1258     }
1259
1260     if (appData.matchMode) {
1261         /* Set up machine vs. machine match */
1262         if (appData.noChessProgram) {
1263             DisplayFatalError(_("Can't have a match with no chess programs"),
1264                               0, 2);
1265             return;
1266         }
1267         matchMode = TRUE;
1268         matchGame = 1;
1269         if (*appData.loadGameFile != NULLCHAR) {
1270             int index = appData.loadGameIndex; // [HGM] autoinc
1271             if(index<0) lastIndex = index = 1;
1272             if (!LoadGameFromFile(appData.loadGameFile,
1273                                   index,
1274                                   appData.loadGameFile, FALSE)) {
1275                 DisplayFatalError(_("Bad game file"), 0, 1);
1276                 return;
1277             }
1278         } else if (*appData.loadPositionFile != NULLCHAR) {
1279             int index = appData.loadPositionIndex; // [HGM] autoinc
1280             if(index<0) lastIndex = index = 1;
1281             if (!LoadPositionFromFile(appData.loadPositionFile,
1282                                       index,
1283                                       appData.loadPositionFile)) {
1284                 DisplayFatalError(_("Bad position file"), 0, 1);
1285                 return;
1286             }
1287         }
1288         TwoMachinesEvent();
1289     } else if (*appData.cmailGameName != NULLCHAR) {
1290         /* Set up cmail mode */
1291         ReloadCmailMsgEvent(TRUE);
1292     } else {
1293         /* Set up other modes */
1294         if (initialMode == AnalyzeFile) {
1295           if (*appData.loadGameFile == NULLCHAR) {
1296             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1297             return;
1298           }
1299         }
1300         if (*appData.loadGameFile != NULLCHAR) {
1301             (void) LoadGameFromFile(appData.loadGameFile,
1302                                     appData.loadGameIndex,
1303                                     appData.loadGameFile, TRUE);
1304         } else if (*appData.loadPositionFile != NULLCHAR) {
1305             (void) LoadPositionFromFile(appData.loadPositionFile,
1306                                         appData.loadPositionIndex,
1307                                         appData.loadPositionFile);
1308             /* [HGM] try to make self-starting even after FEN load */
1309             /* to allow automatic setup of fairy variants with wtm */
1310             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1311                 gameMode = BeginningOfGame;
1312                 setboardSpoiledMachineBlack = 1;
1313             }
1314             /* [HGM] loadPos: make that every new game uses the setup */
1315             /* from file as long as we do not switch variant          */
1316             if(!blackPlaysFirst) {
1317                 startedFromPositionFile = TRUE;
1318                 CopyBoard(filePosition, boards[0]);
1319             }
1320         }
1321         if (initialMode == AnalyzeMode) {
1322           if (appData.noChessProgram) {
1323             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1324             return;
1325           }
1326           if (appData.icsActive) {
1327             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1328             return;
1329           }
1330           AnalyzeModeEvent();
1331         } else if (initialMode == AnalyzeFile) {
1332           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1333           ShowThinkingEvent();
1334           AnalyzeFileEvent();
1335           AnalysisPeriodicEvent(1);
1336         } else if (initialMode == MachinePlaysWhite) {
1337           if (appData.noChessProgram) {
1338             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1339                               0, 2);
1340             return;
1341           }
1342           if (appData.icsActive) {
1343             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1344                               0, 2);
1345             return;
1346           }
1347           MachineWhiteEvent();
1348         } else if (initialMode == MachinePlaysBlack) {
1349           if (appData.noChessProgram) {
1350             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1351                               0, 2);
1352             return;
1353           }
1354           if (appData.icsActive) {
1355             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1356                               0, 2);
1357             return;
1358           }
1359           MachineBlackEvent();
1360         } else if (initialMode == TwoMachinesPlay) {
1361           if (appData.noChessProgram) {
1362             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1363                               0, 2);
1364             return;
1365           }
1366           if (appData.icsActive) {
1367             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1368                               0, 2);
1369             return;
1370           }
1371           TwoMachinesEvent();
1372         } else if (initialMode == EditGame) {
1373           EditGameEvent();
1374         } else if (initialMode == EditPosition) {
1375           EditPositionEvent();
1376         } else if (initialMode == Training) {
1377           if (*appData.loadGameFile == NULLCHAR) {
1378             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1379             return;
1380           }
1381           TrainingEvent();
1382         }
1383     }
1384 }
1385
1386 /*
1387  * Establish will establish a contact to a remote host.port.
1388  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1389  *  used to talk to the host.
1390  * Returns 0 if okay, error code if not.
1391  */
1392 int
1393 establish()
1394 {
1395     char buf[MSG_SIZ];
1396
1397     if (*appData.icsCommPort != NULLCHAR) {
1398         /* Talk to the host through a serial comm port */
1399         return OpenCommPort(appData.icsCommPort, &icsPR);
1400
1401     } else if (*appData.gateway != NULLCHAR) {
1402         if (*appData.remoteShell == NULLCHAR) {
1403             /* Use the rcmd protocol to run telnet program on a gateway host */
1404             snprintf(buf, sizeof(buf), "%s %s %s",
1405                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1406             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1407
1408         } else {
1409             /* Use the rsh program to run telnet program on a gateway host */
1410             if (*appData.remoteUser == NULLCHAR) {
1411                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1412                         appData.gateway, appData.telnetProgram,
1413                         appData.icsHost, appData.icsPort);
1414             } else {
1415                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1416                         appData.remoteShell, appData.gateway,
1417                         appData.remoteUser, appData.telnetProgram,
1418                         appData.icsHost, appData.icsPort);
1419             }
1420             return StartChildProcess(buf, "", &icsPR);
1421
1422         }
1423     } else if (appData.useTelnet) {
1424         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1425
1426     } else {
1427         /* TCP socket interface differs somewhat between
1428            Unix and NT; handle details in the front end.
1429            */
1430         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1431     }
1432 }
1433
1434 void EscapeExpand(char *p, char *q)
1435 {       // [HGM] initstring: routine to shape up string arguments
1436         while(*p++ = *q++) if(p[-1] == '\\')
1437             switch(*q++) {
1438                 case 'n': p[-1] = '\n'; break;
1439                 case 'r': p[-1] = '\r'; break;
1440                 case 't': p[-1] = '\t'; break;
1441                 case '\\': p[-1] = '\\'; break;
1442                 case 0: *p = 0; return;
1443                 default: p[-1] = q[-1]; break;
1444             }
1445 }
1446
1447 void
1448 show_bytes(fp, buf, count)
1449      FILE *fp;
1450      char *buf;
1451      int count;
1452 {
1453     while (count--) {
1454         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1455             fprintf(fp, "\\%03o", *buf & 0xff);
1456         } else {
1457             putc(*buf, fp);
1458         }
1459         buf++;
1460     }
1461     fflush(fp);
1462 }
1463
1464 /* Returns an errno value */
1465 int
1466 OutputMaybeTelnet(pr, message, count, outError)
1467      ProcRef pr;
1468      char *message;
1469      int count;
1470      int *outError;
1471 {
1472     char buf[8192], *p, *q, *buflim;
1473     int left, newcount, outcount;
1474
1475     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1476         *appData.gateway != NULLCHAR) {
1477         if (appData.debugMode) {
1478             fprintf(debugFP, ">ICS: ");
1479             show_bytes(debugFP, message, count);
1480             fprintf(debugFP, "\n");
1481         }
1482         return OutputToProcess(pr, message, count, outError);
1483     }
1484
1485     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1486     p = message;
1487     q = buf;
1488     left = count;
1489     newcount = 0;
1490     while (left) {
1491         if (q >= buflim) {
1492             if (appData.debugMode) {
1493                 fprintf(debugFP, ">ICS: ");
1494                 show_bytes(debugFP, buf, newcount);
1495                 fprintf(debugFP, "\n");
1496             }
1497             outcount = OutputToProcess(pr, buf, newcount, outError);
1498             if (outcount < newcount) return -1; /* to be sure */
1499             q = buf;
1500             newcount = 0;
1501         }
1502         if (*p == '\n') {
1503             *q++ = '\r';
1504             newcount++;
1505         } else if (((unsigned char) *p) == TN_IAC) {
1506             *q++ = (char) TN_IAC;
1507             newcount ++;
1508         }
1509         *q++ = *p++;
1510         newcount++;
1511         left--;
1512     }
1513     if (appData.debugMode) {
1514         fprintf(debugFP, ">ICS: ");
1515         show_bytes(debugFP, buf, newcount);
1516         fprintf(debugFP, "\n");
1517     }
1518     outcount = OutputToProcess(pr, buf, newcount, outError);
1519     if (outcount < newcount) return -1; /* to be sure */
1520     return count;
1521 }
1522
1523 void
1524 read_from_player(isr, closure, message, count, error)
1525      InputSourceRef isr;
1526      VOIDSTAR closure;
1527      char *message;
1528      int count;
1529      int error;
1530 {
1531     int outError, outCount;
1532     static int gotEof = 0;
1533
1534     /* Pass data read from player on to ICS */
1535     if (count > 0) {
1536         gotEof = 0;
1537         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1538         if (outCount < count) {
1539             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1540         }
1541     } else if (count < 0) {
1542         RemoveInputSource(isr);
1543         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1544     } else if (gotEof++ > 0) {
1545         RemoveInputSource(isr);
1546         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1547     }
1548 }
1549
1550 void
1551 KeepAlive()
1552 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1553     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1554     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1555     SendToICS("date\n");
1556     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1557 }
1558
1559 /* added routine for printf style output to ics */
1560 void ics_printf(char *format, ...)
1561 {
1562     char buffer[MSG_SIZ];
1563     va_list args;
1564
1565     va_start(args, format);
1566     vsnprintf(buffer, sizeof(buffer), format, args);
1567     buffer[sizeof(buffer)-1] = '\0';
1568     SendToICS(buffer);
1569     va_end(args);
1570 }
1571
1572 void
1573 SendToICS(s)
1574      char *s;
1575 {
1576     int count, outCount, outError;
1577
1578     if (icsPR == NULL) return;
1579
1580     count = strlen(s);
1581     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1582     if (outCount < count) {
1583         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1584     }
1585 }
1586
1587 /* This is used for sending logon scripts to the ICS. Sending
1588    without a delay causes problems when using timestamp on ICC
1589    (at least on my machine). */
1590 void
1591 SendToICSDelayed(s,msdelay)
1592      char *s;
1593      long msdelay;
1594 {
1595     int count, outCount, outError;
1596
1597     if (icsPR == NULL) return;
1598
1599     count = strlen(s);
1600     if (appData.debugMode) {
1601         fprintf(debugFP, ">ICS: ");
1602         show_bytes(debugFP, s, count);
1603         fprintf(debugFP, "\n");
1604     }
1605     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1606                                       msdelay);
1607     if (outCount < count) {
1608         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1609     }
1610 }
1611
1612
1613 /* Remove all highlighting escape sequences in s
1614    Also deletes any suffix starting with '('
1615    */
1616 char *
1617 StripHighlightAndTitle(s)
1618      char *s;
1619 {
1620     static char retbuf[MSG_SIZ];
1621     char *p = retbuf;
1622
1623     while (*s != NULLCHAR) {
1624         while (*s == '\033') {
1625             while (*s != NULLCHAR && !isalpha(*s)) s++;
1626             if (*s != NULLCHAR) s++;
1627         }
1628         while (*s != NULLCHAR && *s != '\033') {
1629             if (*s == '(' || *s == '[') {
1630                 *p = NULLCHAR;
1631                 return retbuf;
1632             }
1633             *p++ = *s++;
1634         }
1635     }
1636     *p = NULLCHAR;
1637     return retbuf;
1638 }
1639
1640 /* Remove all highlighting escape sequences in s */
1641 char *
1642 StripHighlight(s)
1643      char *s;
1644 {
1645     static char retbuf[MSG_SIZ];
1646     char *p = retbuf;
1647
1648     while (*s != NULLCHAR) {
1649         while (*s == '\033') {
1650             while (*s != NULLCHAR && !isalpha(*s)) s++;
1651             if (*s != NULLCHAR) s++;
1652         }
1653         while (*s != NULLCHAR && *s != '\033') {
1654             *p++ = *s++;
1655         }
1656     }
1657     *p = NULLCHAR;
1658     return retbuf;
1659 }
1660
1661 char *variantNames[] = VARIANT_NAMES;
1662 char *
1663 VariantName(v)
1664      VariantClass v;
1665 {
1666     return variantNames[v];
1667 }
1668
1669
1670 /* Identify a variant from the strings the chess servers use or the
1671    PGN Variant tag names we use. */
1672 VariantClass
1673 StringToVariant(e)
1674      char *e;
1675 {
1676     char *p;
1677     int wnum = -1;
1678     VariantClass v = VariantNormal;
1679     int i, found = FALSE;
1680     char buf[MSG_SIZ];
1681     int len;
1682
1683     if (!e) return v;
1684
1685     /* [HGM] skip over optional board-size prefixes */
1686     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1687         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1688         while( *e++ != '_');
1689     }
1690
1691     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1692         v = VariantNormal;
1693         found = TRUE;
1694     } else
1695     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1696       if (StrCaseStr(e, variantNames[i])) {
1697         v = (VariantClass) i;
1698         found = TRUE;
1699         break;
1700       }
1701     }
1702
1703     if (!found) {
1704       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1705           || StrCaseStr(e, "wild/fr")
1706           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1707         v = VariantFischeRandom;
1708       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1709                  (i = 1, p = StrCaseStr(e, "w"))) {
1710         p += i;
1711         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1712         if (isdigit(*p)) {
1713           wnum = atoi(p);
1714         } else {
1715           wnum = -1;
1716         }
1717         switch (wnum) {
1718         case 0: /* FICS only, actually */
1719         case 1:
1720           /* Castling legal even if K starts on d-file */
1721           v = VariantWildCastle;
1722           break;
1723         case 2:
1724         case 3:
1725         case 4:
1726           /* Castling illegal even if K & R happen to start in
1727              normal positions. */
1728           v = VariantNoCastle;
1729           break;
1730         case 5:
1731         case 7:
1732         case 8:
1733         case 10:
1734         case 11:
1735         case 12:
1736         case 13:
1737         case 14:
1738         case 15:
1739         case 18:
1740         case 19:
1741           /* Castling legal iff K & R start in normal positions */
1742           v = VariantNormal;
1743           break;
1744         case 6:
1745         case 20:
1746         case 21:
1747           /* Special wilds for position setup; unclear what to do here */
1748           v = VariantLoadable;
1749           break;
1750         case 9:
1751           /* Bizarre ICC game */
1752           v = VariantTwoKings;
1753           break;
1754         case 16:
1755           v = VariantKriegspiel;
1756           break;
1757         case 17:
1758           v = VariantLosers;
1759           break;
1760         case 22:
1761           v = VariantFischeRandom;
1762           break;
1763         case 23:
1764           v = VariantCrazyhouse;
1765           break;
1766         case 24:
1767           v = VariantBughouse;
1768           break;
1769         case 25:
1770           v = Variant3Check;
1771           break;
1772         case 26:
1773           /* Not quite the same as FICS suicide! */
1774           v = VariantGiveaway;
1775           break;
1776         case 27:
1777           v = VariantAtomic;
1778           break;
1779         case 28:
1780           v = VariantShatranj;
1781           break;
1782
1783         /* Temporary names for future ICC types.  The name *will* change in
1784            the next xboard/WinBoard release after ICC defines it. */
1785         case 29:
1786           v = Variant29;
1787           break;
1788         case 30:
1789           v = Variant30;
1790           break;
1791         case 31:
1792           v = Variant31;
1793           break;
1794         case 32:
1795           v = Variant32;
1796           break;
1797         case 33:
1798           v = Variant33;
1799           break;
1800         case 34:
1801           v = Variant34;
1802           break;
1803         case 35:
1804           v = Variant35;
1805           break;
1806         case 36:
1807           v = Variant36;
1808           break;
1809         case 37:
1810           v = VariantShogi;
1811           break;
1812         case 38:
1813           v = VariantXiangqi;
1814           break;
1815         case 39:
1816           v = VariantCourier;
1817           break;
1818         case 40:
1819           v = VariantGothic;
1820           break;
1821         case 41:
1822           v = VariantCapablanca;
1823           break;
1824         case 42:
1825           v = VariantKnightmate;
1826           break;
1827         case 43:
1828           v = VariantFairy;
1829           break;
1830         case 44:
1831           v = VariantCylinder;
1832           break;
1833         case 45:
1834           v = VariantFalcon;
1835           break;
1836         case 46:
1837           v = VariantCapaRandom;
1838           break;
1839         case 47:
1840           v = VariantBerolina;
1841           break;
1842         case 48:
1843           v = VariantJanus;
1844           break;
1845         case 49:
1846           v = VariantSuper;
1847           break;
1848         case 50:
1849           v = VariantGreat;
1850           break;
1851         case -1:
1852           /* Found "wild" or "w" in the string but no number;
1853              must assume it's normal chess. */
1854           v = VariantNormal;
1855           break;
1856         default:
1857           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1858           if( (len > MSG_SIZ) && appData.debugMode )
1859             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1860
1861           DisplayError(buf, 0);
1862           v = VariantUnknown;
1863           break;
1864         }
1865       }
1866     }
1867     if (appData.debugMode) {
1868       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1869               e, wnum, VariantName(v));
1870     }
1871     return v;
1872 }
1873
1874 static int leftover_start = 0, leftover_len = 0;
1875 char star_match[STAR_MATCH_N][MSG_SIZ];
1876
1877 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1878    advance *index beyond it, and set leftover_start to the new value of
1879    *index; else return FALSE.  If pattern contains the character '*', it
1880    matches any sequence of characters not containing '\r', '\n', or the
1881    character following the '*' (if any), and the matched sequence(s) are
1882    copied into star_match.
1883    */
1884 int
1885 looking_at(buf, index, pattern)
1886      char *buf;
1887      int *index;
1888      char *pattern;
1889 {
1890     char *bufp = &buf[*index], *patternp = pattern;
1891     int star_count = 0;
1892     char *matchp = star_match[0];
1893
1894     for (;;) {
1895         if (*patternp == NULLCHAR) {
1896             *index = leftover_start = bufp - buf;
1897             *matchp = NULLCHAR;
1898             return TRUE;
1899         }
1900         if (*bufp == NULLCHAR) return FALSE;
1901         if (*patternp == '*') {
1902             if (*bufp == *(patternp + 1)) {
1903                 *matchp = NULLCHAR;
1904                 matchp = star_match[++star_count];
1905                 patternp += 2;
1906                 bufp++;
1907                 continue;
1908             } else if (*bufp == '\n' || *bufp == '\r') {
1909                 patternp++;
1910                 if (*patternp == NULLCHAR)
1911                   continue;
1912                 else
1913                   return FALSE;
1914             } else {
1915                 *matchp++ = *bufp++;
1916                 continue;
1917             }
1918         }
1919         if (*patternp != *bufp) return FALSE;
1920         patternp++;
1921         bufp++;
1922     }
1923 }
1924
1925 void
1926 SendToPlayer(data, length)
1927      char *data;
1928      int length;
1929 {
1930     int error, outCount;
1931     outCount = OutputToProcess(NoProc, data, length, &error);
1932     if (outCount < length) {
1933         DisplayFatalError(_("Error writing to display"), error, 1);
1934     }
1935 }
1936
1937 void
1938 PackHolding(packed, holding)
1939      char packed[];
1940      char *holding;
1941 {
1942     char *p = holding;
1943     char *q = packed;
1944     int runlength = 0;
1945     int curr = 9999;
1946     do {
1947         if (*p == curr) {
1948             runlength++;
1949         } else {
1950             switch (runlength) {
1951               case 0:
1952                 break;
1953               case 1:
1954                 *q++ = curr;
1955                 break;
1956               case 2:
1957                 *q++ = curr;
1958                 *q++ = curr;
1959                 break;
1960               default:
1961                 sprintf(q, "%d", runlength);
1962                 while (*q) q++;
1963                 *q++ = curr;
1964                 break;
1965             }
1966             runlength = 1;
1967             curr = *p;
1968         }
1969     } while (*p++);
1970     *q = NULLCHAR;
1971 }
1972
1973 /* Telnet protocol requests from the front end */
1974 void
1975 TelnetRequest(ddww, option)
1976      unsigned char ddww, option;
1977 {
1978     unsigned char msg[3];
1979     int outCount, outError;
1980
1981     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1982
1983     if (appData.debugMode) {
1984         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1985         switch (ddww) {
1986           case TN_DO:
1987             ddwwStr = "DO";
1988             break;
1989           case TN_DONT:
1990             ddwwStr = "DONT";
1991             break;
1992           case TN_WILL:
1993             ddwwStr = "WILL";
1994             break;
1995           case TN_WONT:
1996             ddwwStr = "WONT";
1997             break;
1998           default:
1999             ddwwStr = buf1;
2000             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2001             break;
2002         }
2003         switch (option) {
2004           case TN_ECHO:
2005             optionStr = "ECHO";
2006             break;
2007           default:
2008             optionStr = buf2;
2009             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2010             break;
2011         }
2012         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2013     }
2014     msg[0] = TN_IAC;
2015     msg[1] = ddww;
2016     msg[2] = option;
2017     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2018     if (outCount < 3) {
2019         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2020     }
2021 }
2022
2023 void
2024 DoEcho()
2025 {
2026     if (!appData.icsActive) return;
2027     TelnetRequest(TN_DO, TN_ECHO);
2028 }
2029
2030 void
2031 DontEcho()
2032 {
2033     if (!appData.icsActive) return;
2034     TelnetRequest(TN_DONT, TN_ECHO);
2035 }
2036
2037 void
2038 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2039 {
2040     /* put the holdings sent to us by the server on the board holdings area */
2041     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2042     char p;
2043     ChessSquare piece;
2044
2045     if(gameInfo.holdingsWidth < 2)  return;
2046     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2047         return; // prevent overwriting by pre-board holdings
2048
2049     if( (int)lowestPiece >= BlackPawn ) {
2050         holdingsColumn = 0;
2051         countsColumn = 1;
2052         holdingsStartRow = BOARD_HEIGHT-1;
2053         direction = -1;
2054     } else {
2055         holdingsColumn = BOARD_WIDTH-1;
2056         countsColumn = BOARD_WIDTH-2;
2057         holdingsStartRow = 0;
2058         direction = 1;
2059     }
2060
2061     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2062         board[i][holdingsColumn] = EmptySquare;
2063         board[i][countsColumn]   = (ChessSquare) 0;
2064     }
2065     while( (p=*holdings++) != NULLCHAR ) {
2066         piece = CharToPiece( ToUpper(p) );
2067         if(piece == EmptySquare) continue;
2068         /*j = (int) piece - (int) WhitePawn;*/
2069         j = PieceToNumber(piece);
2070         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2071         if(j < 0) continue;               /* should not happen */
2072         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2073         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2074         board[holdingsStartRow+j*direction][countsColumn]++;
2075     }
2076 }
2077
2078
2079 void
2080 VariantSwitch(Board board, VariantClass newVariant)
2081 {
2082    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2083    static Board oldBoard;
2084
2085    startedFromPositionFile = FALSE;
2086    if(gameInfo.variant == newVariant) return;
2087
2088    /* [HGM] This routine is called each time an assignment is made to
2089     * gameInfo.variant during a game, to make sure the board sizes
2090     * are set to match the new variant. If that means adding or deleting
2091     * holdings, we shift the playing board accordingly
2092     * This kludge is needed because in ICS observe mode, we get boards
2093     * of an ongoing game without knowing the variant, and learn about the
2094     * latter only later. This can be because of the move list we requested,
2095     * in which case the game history is refilled from the beginning anyway,
2096     * but also when receiving holdings of a crazyhouse game. In the latter
2097     * case we want to add those holdings to the already received position.
2098     */
2099
2100
2101    if (appData.debugMode) {
2102      fprintf(debugFP, "Switch board from %s to %s\n",
2103              VariantName(gameInfo.variant), VariantName(newVariant));
2104      setbuf(debugFP, NULL);
2105    }
2106    shuffleOpenings = 0;       /* [HGM] shuffle */
2107    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2108    switch(newVariant)
2109      {
2110      case VariantShogi:
2111        newWidth = 9;  newHeight = 9;
2112        gameInfo.holdingsSize = 7;
2113      case VariantBughouse:
2114      case VariantCrazyhouse:
2115        newHoldingsWidth = 2; break;
2116      case VariantGreat:
2117        newWidth = 10;
2118      case VariantSuper:
2119        newHoldingsWidth = 2;
2120        gameInfo.holdingsSize = 8;
2121        break;
2122      case VariantGothic:
2123      case VariantCapablanca:
2124      case VariantCapaRandom:
2125        newWidth = 10;
2126      default:
2127        newHoldingsWidth = gameInfo.holdingsSize = 0;
2128      };
2129
2130    if(newWidth  != gameInfo.boardWidth  ||
2131       newHeight != gameInfo.boardHeight ||
2132       newHoldingsWidth != gameInfo.holdingsWidth ) {
2133
2134      /* shift position to new playing area, if needed */
2135      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2136        for(i=0; i<BOARD_HEIGHT; i++)
2137          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2138            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2139              board[i][j];
2140        for(i=0; i<newHeight; i++) {
2141          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2142          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2143        }
2144      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2145        for(i=0; i<BOARD_HEIGHT; i++)
2146          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2147            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2148              board[i][j];
2149      }
2150      gameInfo.boardWidth  = newWidth;
2151      gameInfo.boardHeight = newHeight;
2152      gameInfo.holdingsWidth = newHoldingsWidth;
2153      gameInfo.variant = newVariant;
2154      InitDrawingSizes(-2, 0);
2155    } else gameInfo.variant = newVariant;
2156    CopyBoard(oldBoard, board);   // remember correctly formatted board
2157      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2158    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2159 }
2160
2161 static int loggedOn = FALSE;
2162
2163 /*-- Game start info cache: --*/
2164 int gs_gamenum;
2165 char gs_kind[MSG_SIZ];
2166 static char player1Name[128] = "";
2167 static char player2Name[128] = "";
2168 static char cont_seq[] = "\n\\   ";
2169 static int player1Rating = -1;
2170 static int player2Rating = -1;
2171 /*----------------------------*/
2172
2173 ColorClass curColor = ColorNormal;
2174 int suppressKibitz = 0;
2175
2176 // [HGM] seekgraph
2177 Boolean soughtPending = FALSE;
2178 Boolean seekGraphUp;
2179 #define MAX_SEEK_ADS 200
2180 #define SQUARE 0x80
2181 char *seekAdList[MAX_SEEK_ADS];
2182 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2183 float tcList[MAX_SEEK_ADS];
2184 char colorList[MAX_SEEK_ADS];
2185 int nrOfSeekAds = 0;
2186 int minRating = 1010, maxRating = 2800;
2187 int hMargin = 10, vMargin = 20, h, w;
2188 extern int squareSize, lineGap;
2189
2190 void
2191 PlotSeekAd(int i)
2192 {
2193         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2194         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2195         if(r < minRating+100 && r >=0 ) r = minRating+100;
2196         if(r > maxRating) r = maxRating;
2197         if(tc < 1.) tc = 1.;
2198         if(tc > 95.) tc = 95.;
2199         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2200         y = ((double)r - minRating)/(maxRating - minRating)
2201             * (h-vMargin-squareSize/8-1) + vMargin;
2202         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2203         if(strstr(seekAdList[i], " u ")) color = 1;
2204         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2205            !strstr(seekAdList[i], "bullet") &&
2206            !strstr(seekAdList[i], "blitz") &&
2207            !strstr(seekAdList[i], "standard") ) color = 2;
2208         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2209         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2210 }
2211
2212 void
2213 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2214 {
2215         char buf[MSG_SIZ], *ext = "";
2216         VariantClass v = StringToVariant(type);
2217         if(strstr(type, "wild")) {
2218             ext = type + 4; // append wild number
2219             if(v == VariantFischeRandom) type = "chess960"; else
2220             if(v == VariantLoadable) type = "setup"; else
2221             type = VariantName(v);
2222         }
2223         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2224         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2225             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2226             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2227             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2228             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2229             seekNrList[nrOfSeekAds] = nr;
2230             zList[nrOfSeekAds] = 0;
2231             seekAdList[nrOfSeekAds++] = StrSave(buf);
2232             if(plot) PlotSeekAd(nrOfSeekAds-1);
2233         }
2234 }
2235
2236 void
2237 EraseSeekDot(int i)
2238 {
2239     int x = xList[i], y = yList[i], d=squareSize/4, k;
2240     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2241     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2242     // now replot every dot that overlapped
2243     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2244         int xx = xList[k], yy = yList[k];
2245         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2246             DrawSeekDot(xx, yy, colorList[k]);
2247     }
2248 }
2249
2250 void
2251 RemoveSeekAd(int nr)
2252 {
2253         int i;
2254         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2255             EraseSeekDot(i);
2256             if(seekAdList[i]) free(seekAdList[i]);
2257             seekAdList[i] = seekAdList[--nrOfSeekAds];
2258             seekNrList[i] = seekNrList[nrOfSeekAds];
2259             ratingList[i] = ratingList[nrOfSeekAds];
2260             colorList[i]  = colorList[nrOfSeekAds];
2261             tcList[i] = tcList[nrOfSeekAds];
2262             xList[i]  = xList[nrOfSeekAds];
2263             yList[i]  = yList[nrOfSeekAds];
2264             zList[i]  = zList[nrOfSeekAds];
2265             seekAdList[nrOfSeekAds] = NULL;
2266             break;
2267         }
2268 }
2269
2270 Boolean
2271 MatchSoughtLine(char *line)
2272 {
2273     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2274     int nr, base, inc, u=0; char dummy;
2275
2276     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2277        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2278        (u=1) &&
2279        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2280         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2281         // match: compact and save the line
2282         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2283         return TRUE;
2284     }
2285     return FALSE;
2286 }
2287
2288 int
2289 DrawSeekGraph()
2290 {
2291     int i;
2292     if(!seekGraphUp) return FALSE;
2293     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2294     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2295
2296     DrawSeekBackground(0, 0, w, h);
2297     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2298     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2299     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2300         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2301         yy = h-1-yy;
2302         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2303         if(i%500 == 0) {
2304             char buf[MSG_SIZ];
2305             snprintf(buf, MSG_SIZ, "%d", i);
2306             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2307         }
2308     }
2309     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2310     for(i=1; i<100; i+=(i<10?1:5)) {
2311         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2312         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2313         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2314             char buf[MSG_SIZ];
2315             snprintf(buf, MSG_SIZ, "%d", i);
2316             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2317         }
2318     }
2319     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2320     return TRUE;
2321 }
2322
2323 int SeekGraphClick(ClickType click, int x, int y, int moving)
2324 {
2325     static int lastDown = 0, displayed = 0, lastSecond;
2326     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2327         if(click == Release || moving) return FALSE;
2328         nrOfSeekAds = 0;
2329         soughtPending = TRUE;
2330         SendToICS(ics_prefix);
2331         SendToICS("sought\n"); // should this be "sought all"?
2332     } else { // issue challenge based on clicked ad
2333         int dist = 10000; int i, closest = 0, second = 0;
2334         for(i=0; i<nrOfSeekAds; i++) {
2335             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2336             if(d < dist) { dist = d; closest = i; }
2337             second += (d - zList[i] < 120); // count in-range ads
2338             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2339         }
2340         if(dist < 120) {
2341             char buf[MSG_SIZ];
2342             second = (second > 1);
2343             if(displayed != closest || second != lastSecond) {
2344                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2345                 lastSecond = second; displayed = closest;
2346             }
2347             if(click == Press) {
2348                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2349                 lastDown = closest;
2350                 return TRUE;
2351             } // on press 'hit', only show info
2352             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2353             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2354             SendToICS(ics_prefix);
2355             SendToICS(buf);
2356             return TRUE; // let incoming board of started game pop down the graph
2357         } else if(click == Release) { // release 'miss' is ignored
2358             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2359             if(moving == 2) { // right up-click
2360                 nrOfSeekAds = 0; // refresh graph
2361                 soughtPending = TRUE;
2362                 SendToICS(ics_prefix);
2363                 SendToICS("sought\n"); // should this be "sought all"?
2364             }
2365             return TRUE;
2366         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2367         // press miss or release hit 'pop down' seek graph
2368         seekGraphUp = FALSE;
2369         DrawPosition(TRUE, NULL);
2370     }
2371     return TRUE;
2372 }
2373
2374 void
2375 read_from_ics(isr, closure, data, count, error)
2376      InputSourceRef isr;
2377      VOIDSTAR closure;
2378      char *data;
2379      int count;
2380      int error;
2381 {
2382 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2383 #define STARTED_NONE 0
2384 #define STARTED_MOVES 1
2385 #define STARTED_BOARD 2
2386 #define STARTED_OBSERVE 3
2387 #define STARTED_HOLDINGS 4
2388 #define STARTED_CHATTER 5
2389 #define STARTED_COMMENT 6
2390 #define STARTED_MOVES_NOHIDE 7
2391
2392     static int started = STARTED_NONE;
2393     static char parse[20000];
2394     static int parse_pos = 0;
2395     static char buf[BUF_SIZE + 1];
2396     static int firstTime = TRUE, intfSet = FALSE;
2397     static ColorClass prevColor = ColorNormal;
2398     static int savingComment = FALSE;
2399     static int cmatch = 0; // continuation sequence match
2400     char *bp;
2401     char str[MSG_SIZ];
2402     int i, oldi;
2403     int buf_len;
2404     int next_out;
2405     int tkind;
2406     int backup;    /* [DM] For zippy color lines */
2407     char *p;
2408     char talker[MSG_SIZ]; // [HGM] chat
2409     int channel;
2410
2411     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2412
2413     if (appData.debugMode) {
2414       if (!error) {
2415         fprintf(debugFP, "<ICS: ");
2416         show_bytes(debugFP, data, count);
2417         fprintf(debugFP, "\n");
2418       }
2419     }
2420
2421     if (appData.debugMode) { int f = forwardMostMove;
2422         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2423                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2424                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2425     }
2426     if (count > 0) {
2427         /* If last read ended with a partial line that we couldn't parse,
2428            prepend it to the new read and try again. */
2429         if (leftover_len > 0) {
2430             for (i=0; i<leftover_len; i++)
2431               buf[i] = buf[leftover_start + i];
2432         }
2433
2434     /* copy new characters into the buffer */
2435     bp = buf + leftover_len;
2436     buf_len=leftover_len;
2437     for (i=0; i<count; i++)
2438     {
2439         // ignore these
2440         if (data[i] == '\r')
2441             continue;
2442
2443         // join lines split by ICS?
2444         if (!appData.noJoin)
2445         {
2446             /*
2447                 Joining just consists of finding matches against the
2448                 continuation sequence, and discarding that sequence
2449                 if found instead of copying it.  So, until a match
2450                 fails, there's nothing to do since it might be the
2451                 complete sequence, and thus, something we don't want
2452                 copied.
2453             */
2454             if (data[i] == cont_seq[cmatch])
2455             {
2456                 cmatch++;
2457                 if (cmatch == strlen(cont_seq))
2458                 {
2459                     cmatch = 0; // complete match.  just reset the counter
2460
2461                     /*
2462                         it's possible for the ICS to not include the space
2463                         at the end of the last word, making our [correct]
2464                         join operation fuse two separate words.  the server
2465                         does this when the space occurs at the width setting.
2466                     */
2467                     if (!buf_len || buf[buf_len-1] != ' ')
2468                     {
2469                         *bp++ = ' ';
2470                         buf_len++;
2471                     }
2472                 }
2473                 continue;
2474             }
2475             else if (cmatch)
2476             {
2477                 /*
2478                     match failed, so we have to copy what matched before
2479                     falling through and copying this character.  In reality,
2480                     this will only ever be just the newline character, but
2481                     it doesn't hurt to be precise.
2482                 */
2483                 strncpy(bp, cont_seq, cmatch);
2484                 bp += cmatch;
2485                 buf_len += cmatch;
2486                 cmatch = 0;
2487             }
2488         }
2489
2490         // copy this char
2491         *bp++ = data[i];
2492         buf_len++;
2493     }
2494
2495         buf[buf_len] = NULLCHAR;
2496 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2497         next_out = 0;
2498         leftover_start = 0;
2499
2500         i = 0;
2501         while (i < buf_len) {
2502             /* Deal with part of the TELNET option negotiation
2503                protocol.  We refuse to do anything beyond the
2504                defaults, except that we allow the WILL ECHO option,
2505                which ICS uses to turn off password echoing when we are
2506                directly connected to it.  We reject this option
2507                if localLineEditing mode is on (always on in xboard)
2508                and we are talking to port 23, which might be a real
2509                telnet server that will try to keep WILL ECHO on permanently.
2510              */
2511             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2512                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2513                 unsigned char option;
2514                 oldi = i;
2515                 switch ((unsigned char) buf[++i]) {
2516                   case TN_WILL:
2517                     if (appData.debugMode)
2518                       fprintf(debugFP, "\n<WILL ");
2519                     switch (option = (unsigned char) buf[++i]) {
2520                       case TN_ECHO:
2521                         if (appData.debugMode)
2522                           fprintf(debugFP, "ECHO ");
2523                         /* Reply only if this is a change, according
2524                            to the protocol rules. */
2525                         if (remoteEchoOption) break;
2526                         if (appData.localLineEditing &&
2527                             atoi(appData.icsPort) == TN_PORT) {
2528                             TelnetRequest(TN_DONT, TN_ECHO);
2529                         } else {
2530                             EchoOff();
2531                             TelnetRequest(TN_DO, TN_ECHO);
2532                             remoteEchoOption = TRUE;
2533                         }
2534                         break;
2535                       default:
2536                         if (appData.debugMode)
2537                           fprintf(debugFP, "%d ", option);
2538                         /* Whatever this is, we don't want it. */
2539                         TelnetRequest(TN_DONT, option);
2540                         break;
2541                     }
2542                     break;
2543                   case TN_WONT:
2544                     if (appData.debugMode)
2545                       fprintf(debugFP, "\n<WONT ");
2546                     switch (option = (unsigned char) buf[++i]) {
2547                       case TN_ECHO:
2548                         if (appData.debugMode)
2549                           fprintf(debugFP, "ECHO ");
2550                         /* Reply only if this is a change, according
2551                            to the protocol rules. */
2552                         if (!remoteEchoOption) break;
2553                         EchoOn();
2554                         TelnetRequest(TN_DONT, TN_ECHO);
2555                         remoteEchoOption = FALSE;
2556                         break;
2557                       default:
2558                         if (appData.debugMode)
2559                           fprintf(debugFP, "%d ", (unsigned char) option);
2560                         /* Whatever this is, it must already be turned
2561                            off, because we never agree to turn on
2562                            anything non-default, so according to the
2563                            protocol rules, we don't reply. */
2564                         break;
2565                     }
2566                     break;
2567                   case TN_DO:
2568                     if (appData.debugMode)
2569                       fprintf(debugFP, "\n<DO ");
2570                     switch (option = (unsigned char) buf[++i]) {
2571                       default:
2572                         /* Whatever this is, we refuse to do it. */
2573                         if (appData.debugMode)
2574                           fprintf(debugFP, "%d ", option);
2575                         TelnetRequest(TN_WONT, option);
2576                         break;
2577                     }
2578                     break;
2579                   case TN_DONT:
2580                     if (appData.debugMode)
2581                       fprintf(debugFP, "\n<DONT ");
2582                     switch (option = (unsigned char) buf[++i]) {
2583                       default:
2584                         if (appData.debugMode)
2585                           fprintf(debugFP, "%d ", option);
2586                         /* Whatever this is, we are already not doing
2587                            it, because we never agree to do anything
2588                            non-default, so according to the protocol
2589                            rules, we don't reply. */
2590                         break;
2591                     }
2592                     break;
2593                   case TN_IAC:
2594                     if (appData.debugMode)
2595                       fprintf(debugFP, "\n<IAC ");
2596                     /* Doubled IAC; pass it through */
2597                     i--;
2598                     break;
2599                   default:
2600                     if (appData.debugMode)
2601                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2602                     /* Drop all other telnet commands on the floor */
2603                     break;
2604                 }
2605                 if (oldi > next_out)
2606                   SendToPlayer(&buf[next_out], oldi - next_out);
2607                 if (++i > next_out)
2608                   next_out = i;
2609                 continue;
2610             }
2611
2612             /* OK, this at least will *usually* work */
2613             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2614                 loggedOn = TRUE;
2615             }
2616
2617             if (loggedOn && !intfSet) {
2618                 if (ics_type == ICS_ICC) {
2619                   snprintf(str, MSG_SIZ,
2620                           "/set-quietly interface %s\n/set-quietly style 12\n",
2621                           programVersion);
2622                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2623                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2624                 } else if (ics_type == ICS_CHESSNET) {
2625                   snprintf(str, MSG_SIZ, "/style 12\n");
2626                 } else {
2627                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2628                   strcat(str, programVersion);
2629                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2630                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2631                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2632 #ifdef WIN32
2633                   strcat(str, "$iset nohighlight 1\n");
2634 #endif
2635                   strcat(str, "$iset lock 1\n$style 12\n");
2636                 }
2637                 SendToICS(str);
2638                 NotifyFrontendLogin();
2639                 intfSet = TRUE;
2640             }
2641
2642             if (started == STARTED_COMMENT) {
2643                 /* Accumulate characters in comment */
2644                 parse[parse_pos++] = buf[i];
2645                 if (buf[i] == '\n') {
2646                     parse[parse_pos] = NULLCHAR;
2647                     if(chattingPartner>=0) {
2648                         char mess[MSG_SIZ];
2649                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2650                         OutputChatMessage(chattingPartner, mess);
2651                         chattingPartner = -1;
2652                         next_out = i+1; // [HGM] suppress printing in ICS window
2653                     } else
2654                     if(!suppressKibitz) // [HGM] kibitz
2655                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2656                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2657                         int nrDigit = 0, nrAlph = 0, j;
2658                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2659                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2660                         parse[parse_pos] = NULLCHAR;
2661                         // try to be smart: if it does not look like search info, it should go to
2662                         // ICS interaction window after all, not to engine-output window.
2663                         for(j=0; j<parse_pos; j++) { // count letters and digits
2664                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2665                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2666                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2667                         }
2668                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2669                             int depth=0; float score;
2670                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2671                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2672                                 pvInfoList[forwardMostMove-1].depth = depth;
2673                                 pvInfoList[forwardMostMove-1].score = 100*score;
2674                             }
2675                             OutputKibitz(suppressKibitz, parse);
2676                         } else {
2677                             char tmp[MSG_SIZ];
2678                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2679                             SendToPlayer(tmp, strlen(tmp));
2680                         }
2681                         next_out = i+1; // [HGM] suppress printing in ICS window
2682                     }
2683                     started = STARTED_NONE;
2684                 } else {
2685                     /* Don't match patterns against characters in comment */
2686                     i++;
2687                     continue;
2688                 }
2689             }
2690             if (started == STARTED_CHATTER) {
2691                 if (buf[i] != '\n') {
2692                     /* Don't match patterns against characters in chatter */
2693                     i++;
2694                     continue;
2695                 }
2696                 started = STARTED_NONE;
2697                 if(suppressKibitz) next_out = i+1;
2698             }
2699
2700             /* Kludge to deal with rcmd protocol */
2701             if (firstTime && looking_at(buf, &i, "\001*")) {
2702                 DisplayFatalError(&buf[1], 0, 1);
2703                 continue;
2704             } else {
2705                 firstTime = FALSE;
2706             }
2707
2708             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2709                 ics_type = ICS_ICC;
2710                 ics_prefix = "/";
2711                 if (appData.debugMode)
2712                   fprintf(debugFP, "ics_type %d\n", ics_type);
2713                 continue;
2714             }
2715             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2716                 ics_type = ICS_FICS;
2717                 ics_prefix = "$";
2718                 if (appData.debugMode)
2719                   fprintf(debugFP, "ics_type %d\n", ics_type);
2720                 continue;
2721             }
2722             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2723                 ics_type = ICS_CHESSNET;
2724                 ics_prefix = "/";
2725                 if (appData.debugMode)
2726                   fprintf(debugFP, "ics_type %d\n", ics_type);
2727                 continue;
2728             }
2729
2730             if (!loggedOn &&
2731                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2732                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2733                  looking_at(buf, &i, "will be \"*\""))) {
2734               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2735               continue;
2736             }
2737
2738             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2739               char buf[MSG_SIZ];
2740               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2741               DisplayIcsInteractionTitle(buf);
2742               have_set_title = TRUE;
2743             }
2744
2745             /* skip finger notes */
2746             if (started == STARTED_NONE &&
2747                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2748                  (buf[i] == '1' && buf[i+1] == '0')) &&
2749                 buf[i+2] == ':' && buf[i+3] == ' ') {
2750               started = STARTED_CHATTER;
2751               i += 3;
2752               continue;
2753             }
2754
2755             oldi = i;
2756             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2757             if(appData.seekGraph) {
2758                 if(soughtPending && MatchSoughtLine(buf+i)) {
2759                     i = strstr(buf+i, "rated") - buf;
2760                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2761                     next_out = leftover_start = i;
2762                     started = STARTED_CHATTER;
2763                     suppressKibitz = TRUE;
2764                     continue;
2765                 }
2766                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2767                         && looking_at(buf, &i, "* ads displayed")) {
2768                     soughtPending = FALSE;
2769                     seekGraphUp = TRUE;
2770                     DrawSeekGraph();
2771                     continue;
2772                 }
2773                 if(appData.autoRefresh) {
2774                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2775                         int s = (ics_type == ICS_ICC); // ICC format differs
2776                         if(seekGraphUp)
2777                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2778                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2779                         looking_at(buf, &i, "*% "); // eat prompt
2780                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2781                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2782                         next_out = i; // suppress
2783                         continue;
2784                     }
2785                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2786                         char *p = star_match[0];
2787                         while(*p) {
2788                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2789                             while(*p && *p++ != ' '); // next
2790                         }
2791                         looking_at(buf, &i, "*% "); // eat prompt
2792                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2793                         next_out = i;
2794                         continue;
2795                     }
2796                 }
2797             }
2798
2799             /* skip formula vars */
2800             if (started == STARTED_NONE &&
2801                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2802               started = STARTED_CHATTER;
2803               i += 3;
2804               continue;
2805             }
2806
2807             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2808             if (appData.autoKibitz && started == STARTED_NONE &&
2809                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2810                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2811                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2812                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2813                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2814                         suppressKibitz = TRUE;
2815                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2816                         next_out = i;
2817                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2818                                 && (gameMode == IcsPlayingWhite)) ||
2819                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2820                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2821                             started = STARTED_CHATTER; // own kibitz we simply discard
2822                         else {
2823                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2824                             parse_pos = 0; parse[0] = NULLCHAR;
2825                             savingComment = TRUE;
2826                             suppressKibitz = gameMode != IcsObserving ? 2 :
2827                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2828                         }
2829                         continue;
2830                 } else
2831                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2832                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2833                          && atoi(star_match[0])) {
2834                     // suppress the acknowledgements of our own autoKibitz
2835                     char *p;
2836                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2837                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2838                     SendToPlayer(star_match[0], strlen(star_match[0]));
2839                     if(looking_at(buf, &i, "*% ")) // eat prompt
2840                         suppressKibitz = FALSE;
2841                     next_out = i;
2842                     continue;
2843                 }
2844             } // [HGM] kibitz: end of patch
2845
2846             // [HGM] chat: intercept tells by users for which we have an open chat window
2847             channel = -1;
2848             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2849                                            looking_at(buf, &i, "* whispers:") ||
2850                                            looking_at(buf, &i, "* kibitzes:") ||
2851                                            looking_at(buf, &i, "* shouts:") ||
2852                                            looking_at(buf, &i, "* c-shouts:") ||
2853                                            looking_at(buf, &i, "--> * ") ||
2854                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2855                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2856                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2857                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2858                 int p;
2859                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2860                 chattingPartner = -1;
2861
2862                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2863                 for(p=0; p<MAX_CHAT; p++) {
2864                     if(channel == atoi(chatPartner[p])) {
2865                     talker[0] = '['; strcat(talker, "] ");
2866                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2867                     chattingPartner = p; break;
2868                     }
2869                 } else
2870                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2871                 for(p=0; p<MAX_CHAT; p++) {
2872                     if(!strcmp("kibitzes", chatPartner[p])) {
2873                         talker[0] = '['; strcat(talker, "] ");
2874                         chattingPartner = p; break;
2875                     }
2876                 } else
2877                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2878                 for(p=0; p<MAX_CHAT; p++) {
2879                     if(!strcmp("whispers", chatPartner[p])) {
2880                         talker[0] = '['; strcat(talker, "] ");
2881                         chattingPartner = p; break;
2882                     }
2883                 } else
2884                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2885                   if(buf[i-8] == '-' && buf[i-3] == 't')
2886                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2887                     if(!strcmp("c-shouts", chatPartner[p])) {
2888                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2889                         chattingPartner = p; break;
2890                     }
2891                   }
2892                   if(chattingPartner < 0)
2893                   for(p=0; p<MAX_CHAT; p++) {
2894                     if(!strcmp("shouts", chatPartner[p])) {
2895                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2896                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2897                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2898                         chattingPartner = p; break;
2899                     }
2900                   }
2901                 }
2902                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2903                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2904                     talker[0] = 0; Colorize(ColorTell, FALSE);
2905                     chattingPartner = p; break;
2906                 }
2907                 if(chattingPartner<0) i = oldi; else {
2908                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2909                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2910                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2911                     started = STARTED_COMMENT;
2912                     parse_pos = 0; parse[0] = NULLCHAR;
2913                     savingComment = 3 + chattingPartner; // counts as TRUE
2914                     suppressKibitz = TRUE;
2915                     continue;
2916                 }
2917             } // [HGM] chat: end of patch
2918
2919             if (appData.zippyTalk || appData.zippyPlay) {
2920                 /* [DM] Backup address for color zippy lines */
2921                 backup = i;
2922 #if ZIPPY
2923                if (loggedOn == TRUE)
2924                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2925                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2926 #endif
2927             } // [DM] 'else { ' deleted
2928                 if (
2929                     /* Regular tells and says */
2930                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2931                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2932                     looking_at(buf, &i, "* says: ") ||
2933                     /* Don't color "message" or "messages" output */
2934                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2935                     looking_at(buf, &i, "*. * at *:*: ") ||
2936                     looking_at(buf, &i, "--* (*:*): ") ||
2937                     /* Message notifications (same color as tells) */
2938                     looking_at(buf, &i, "* has left a message ") ||
2939                     looking_at(buf, &i, "* just sent you a message:\n") ||
2940                     /* Whispers and kibitzes */
2941                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2942                     looking_at(buf, &i, "* kibitzes: ") ||
2943                     /* Channel tells */
2944                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2945
2946                   if (tkind == 1 && strchr(star_match[0], ':')) {
2947                       /* Avoid "tells you:" spoofs in channels */
2948                      tkind = 3;
2949                   }
2950                   if (star_match[0][0] == NULLCHAR ||
2951                       strchr(star_match[0], ' ') ||
2952                       (tkind == 3 && strchr(star_match[1], ' '))) {
2953                     /* Reject bogus matches */
2954                     i = oldi;
2955                   } else {
2956                     if (appData.colorize) {
2957                       if (oldi > next_out) {
2958                         SendToPlayer(&buf[next_out], oldi - next_out);
2959                         next_out = oldi;
2960                       }
2961                       switch (tkind) {
2962                       case 1:
2963                         Colorize(ColorTell, FALSE);
2964                         curColor = ColorTell;
2965                         break;
2966                       case 2:
2967                         Colorize(ColorKibitz, FALSE);
2968                         curColor = ColorKibitz;
2969                         break;
2970                       case 3:
2971                         p = strrchr(star_match[1], '(');
2972                         if (p == NULL) {
2973                           p = star_match[1];
2974                         } else {
2975                           p++;
2976                         }
2977                         if (atoi(p) == 1) {
2978                           Colorize(ColorChannel1, FALSE);
2979                           curColor = ColorChannel1;
2980                         } else {
2981                           Colorize(ColorChannel, FALSE);
2982                           curColor = ColorChannel;
2983                         }
2984                         break;
2985                       case 5:
2986                         curColor = ColorNormal;
2987                         break;
2988                       }
2989                     }
2990                     if (started == STARTED_NONE && appData.autoComment &&
2991                         (gameMode == IcsObserving ||
2992                          gameMode == IcsPlayingWhite ||
2993                          gameMode == IcsPlayingBlack)) {
2994                       parse_pos = i - oldi;
2995                       memcpy(parse, &buf[oldi], parse_pos);
2996                       parse[parse_pos] = NULLCHAR;
2997                       started = STARTED_COMMENT;
2998                       savingComment = TRUE;
2999                     } else {
3000                       started = STARTED_CHATTER;
3001                       savingComment = FALSE;
3002                     }
3003                     loggedOn = TRUE;
3004                     continue;
3005                   }
3006                 }
3007
3008                 if (looking_at(buf, &i, "* s-shouts: ") ||
3009                     looking_at(buf, &i, "* c-shouts: ")) {
3010                     if (appData.colorize) {
3011                         if (oldi > next_out) {
3012                             SendToPlayer(&buf[next_out], oldi - next_out);
3013                             next_out = oldi;
3014                         }
3015                         Colorize(ColorSShout, FALSE);
3016                         curColor = ColorSShout;
3017                     }
3018                     loggedOn = TRUE;
3019                     started = STARTED_CHATTER;
3020                     continue;
3021                 }
3022
3023                 if (looking_at(buf, &i, "--->")) {
3024                     loggedOn = TRUE;
3025                     continue;
3026                 }
3027
3028                 if (looking_at(buf, &i, "* shouts: ") ||
3029                     looking_at(buf, &i, "--> ")) {
3030                     if (appData.colorize) {
3031                         if (oldi > next_out) {
3032                             SendToPlayer(&buf[next_out], oldi - next_out);
3033                             next_out = oldi;
3034                         }
3035                         Colorize(ColorShout, FALSE);
3036                         curColor = ColorShout;
3037                     }
3038                     loggedOn = TRUE;
3039                     started = STARTED_CHATTER;
3040                     continue;
3041                 }
3042
3043                 if (looking_at( buf, &i, "Challenge:")) {
3044                     if (appData.colorize) {
3045                         if (oldi > next_out) {
3046                             SendToPlayer(&buf[next_out], oldi - next_out);
3047                             next_out = oldi;
3048                         }
3049                         Colorize(ColorChallenge, FALSE);
3050                         curColor = ColorChallenge;
3051                     }
3052                     loggedOn = TRUE;
3053                     continue;
3054                 }
3055
3056                 if (looking_at(buf, &i, "* offers you") ||
3057                     looking_at(buf, &i, "* offers to be") ||
3058                     looking_at(buf, &i, "* would like to") ||
3059                     looking_at(buf, &i, "* requests to") ||
3060                     looking_at(buf, &i, "Your opponent offers") ||
3061                     looking_at(buf, &i, "Your opponent requests")) {
3062
3063                     if (appData.colorize) {
3064                         if (oldi > next_out) {
3065                             SendToPlayer(&buf[next_out], oldi - next_out);
3066                             next_out = oldi;
3067                         }
3068                         Colorize(ColorRequest, FALSE);
3069                         curColor = ColorRequest;
3070                     }
3071                     continue;
3072                 }
3073
3074                 if (looking_at(buf, &i, "* (*) seeking")) {
3075                     if (appData.colorize) {
3076                         if (oldi > next_out) {
3077                             SendToPlayer(&buf[next_out], oldi - next_out);
3078                             next_out = oldi;
3079                         }
3080                         Colorize(ColorSeek, FALSE);
3081                         curColor = ColorSeek;
3082                     }
3083                     continue;
3084             }
3085
3086             if (looking_at(buf, &i, "\\   ")) {
3087                 if (prevColor != ColorNormal) {
3088                     if (oldi > next_out) {
3089                         SendToPlayer(&buf[next_out], oldi - next_out);
3090                         next_out = oldi;
3091                     }
3092                     Colorize(prevColor, TRUE);
3093                     curColor = prevColor;
3094                 }
3095                 if (savingComment) {
3096                     parse_pos = i - oldi;
3097                     memcpy(parse, &buf[oldi], parse_pos);
3098                     parse[parse_pos] = NULLCHAR;
3099                     started = STARTED_COMMENT;
3100                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3101                         chattingPartner = savingComment - 3; // kludge to remember the box
3102                 } else {
3103                     started = STARTED_CHATTER;
3104                 }
3105                 continue;
3106             }
3107
3108             if (looking_at(buf, &i, "Black Strength :") ||
3109                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3110                 looking_at(buf, &i, "<10>") ||
3111                 looking_at(buf, &i, "#@#")) {
3112                 /* Wrong board style */
3113                 loggedOn = TRUE;
3114                 SendToICS(ics_prefix);
3115                 SendToICS("set style 12\n");
3116                 SendToICS(ics_prefix);
3117                 SendToICS("refresh\n");
3118                 continue;
3119             }
3120
3121             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3122                 ICSInitScript();
3123                 have_sent_ICS_logon = 1;
3124                 continue;
3125             }
3126
3127             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3128                 (looking_at(buf, &i, "\n<12> ") ||
3129                  looking_at(buf, &i, "<12> "))) {
3130                 loggedOn = TRUE;
3131                 if (oldi > next_out) {
3132                     SendToPlayer(&buf[next_out], oldi - next_out);
3133                 }
3134                 next_out = i;
3135                 started = STARTED_BOARD;
3136                 parse_pos = 0;
3137                 continue;
3138             }
3139
3140             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3141                 looking_at(buf, &i, "<b1> ")) {
3142                 if (oldi > next_out) {
3143                     SendToPlayer(&buf[next_out], oldi - next_out);
3144                 }
3145                 next_out = i;
3146                 started = STARTED_HOLDINGS;
3147                 parse_pos = 0;
3148                 continue;
3149             }
3150
3151             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3152                 loggedOn = TRUE;
3153                 /* Header for a move list -- first line */
3154
3155                 switch (ics_getting_history) {
3156                   case H_FALSE:
3157                     switch (gameMode) {
3158                       case IcsIdle:
3159                       case BeginningOfGame:
3160                         /* User typed "moves" or "oldmoves" while we
3161                            were idle.  Pretend we asked for these
3162                            moves and soak them up so user can step
3163                            through them and/or save them.
3164                            */
3165                         Reset(FALSE, TRUE);
3166                         gameMode = IcsObserving;
3167                         ModeHighlight();
3168                         ics_gamenum = -1;
3169                         ics_getting_history = H_GOT_UNREQ_HEADER;
3170                         break;
3171                       case EditGame: /*?*/
3172                       case EditPosition: /*?*/
3173                         /* Should above feature work in these modes too? */
3174                         /* For now it doesn't */
3175                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3176                         break;
3177                       default:
3178                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3179                         break;
3180                     }
3181                     break;
3182                   case H_REQUESTED:
3183                     /* Is this the right one? */
3184                     if (gameInfo.white && gameInfo.black &&
3185                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3186                         strcmp(gameInfo.black, star_match[2]) == 0) {
3187                         /* All is well */
3188                         ics_getting_history = H_GOT_REQ_HEADER;
3189                     }
3190                     break;
3191                   case H_GOT_REQ_HEADER:
3192                   case H_GOT_UNREQ_HEADER:
3193                   case H_GOT_UNWANTED_HEADER:
3194                   case H_GETTING_MOVES:
3195                     /* Should not happen */
3196                     DisplayError(_("Error gathering move list: two headers"), 0);
3197                     ics_getting_history = H_FALSE;
3198                     break;
3199                 }
3200
3201                 /* Save player ratings into gameInfo if needed */
3202                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3203                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3204                     (gameInfo.whiteRating == -1 ||
3205                      gameInfo.blackRating == -1)) {
3206
3207                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3208                     gameInfo.blackRating = string_to_rating(star_match[3]);
3209                     if (appData.debugMode)
3210                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3211                               gameInfo.whiteRating, gameInfo.blackRating);
3212                 }
3213                 continue;
3214             }
3215
3216             if (looking_at(buf, &i,
3217               "* * match, initial time: * minute*, increment: * second")) {
3218                 /* Header for a move list -- second line */
3219                 /* Initial board will follow if this is a wild game */
3220                 if (gameInfo.event != NULL) free(gameInfo.event);
3221                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3222                 gameInfo.event = StrSave(str);
3223                 /* [HGM] we switched variant. Translate boards if needed. */
3224                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3225                 continue;
3226             }
3227
3228             if (looking_at(buf, &i, "Move  ")) {
3229                 /* Beginning of a move list */
3230                 switch (ics_getting_history) {
3231                   case H_FALSE:
3232                     /* Normally should not happen */
3233                     /* Maybe user hit reset while we were parsing */
3234                     break;
3235                   case H_REQUESTED:
3236                     /* Happens if we are ignoring a move list that is not
3237                      * the one we just requested.  Common if the user
3238                      * tries to observe two games without turning off
3239                      * getMoveList */
3240                     break;
3241                   case H_GETTING_MOVES:
3242                     /* Should not happen */
3243                     DisplayError(_("Error gathering move list: nested"), 0);
3244                     ics_getting_history = H_FALSE;
3245                     break;
3246                   case H_GOT_REQ_HEADER:
3247                     ics_getting_history = H_GETTING_MOVES;
3248                     started = STARTED_MOVES;
3249                     parse_pos = 0;
3250                     if (oldi > next_out) {
3251                         SendToPlayer(&buf[next_out], oldi - next_out);
3252                     }
3253                     break;
3254                   case H_GOT_UNREQ_HEADER:
3255                     ics_getting_history = H_GETTING_MOVES;
3256                     started = STARTED_MOVES_NOHIDE;
3257                     parse_pos = 0;
3258                     break;
3259                   case H_GOT_UNWANTED_HEADER:
3260                     ics_getting_history = H_FALSE;
3261                     break;
3262                 }
3263                 continue;
3264             }
3265
3266             if (looking_at(buf, &i, "% ") ||
3267                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3268                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3269                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3270                     soughtPending = FALSE;
3271                     seekGraphUp = TRUE;
3272                     DrawSeekGraph();
3273                 }
3274                 if(suppressKibitz) next_out = i;
3275                 savingComment = FALSE;
3276                 suppressKibitz = 0;
3277                 switch (started) {
3278                   case STARTED_MOVES:
3279                   case STARTED_MOVES_NOHIDE:
3280                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3281                     parse[parse_pos + i - oldi] = NULLCHAR;
3282                     ParseGameHistory(parse);
3283 #if ZIPPY
3284                     if (appData.zippyPlay && first.initDone) {
3285                         FeedMovesToProgram(&first, forwardMostMove);
3286                         if (gameMode == IcsPlayingWhite) {
3287                             if (WhiteOnMove(forwardMostMove)) {
3288                                 if (first.sendTime) {
3289                                   if (first.useColors) {
3290                                     SendToProgram("black\n", &first);
3291                                   }
3292                                   SendTimeRemaining(&first, TRUE);
3293                                 }
3294                                 if (first.useColors) {
3295                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3296                                 }
3297                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3298                                 first.maybeThinking = TRUE;
3299                             } else {
3300                                 if (first.usePlayother) {
3301                                   if (first.sendTime) {
3302                                     SendTimeRemaining(&first, TRUE);
3303                                   }
3304                                   SendToProgram("playother\n", &first);
3305                                   firstMove = FALSE;
3306                                 } else {
3307                                   firstMove = TRUE;
3308                                 }
3309                             }
3310                         } else if (gameMode == IcsPlayingBlack) {
3311                             if (!WhiteOnMove(forwardMostMove)) {
3312                                 if (first.sendTime) {
3313                                   if (first.useColors) {
3314                                     SendToProgram("white\n", &first);
3315                                   }
3316                                   SendTimeRemaining(&first, FALSE);
3317                                 }
3318                                 if (first.useColors) {
3319                                   SendToProgram("black\n", &first);
3320                                 }
3321                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3322                                 first.maybeThinking = TRUE;
3323                             } else {
3324                                 if (first.usePlayother) {
3325                                   if (first.sendTime) {
3326                                     SendTimeRemaining(&first, FALSE);
3327                                   }
3328                                   SendToProgram("playother\n", &first);
3329                                   firstMove = FALSE;
3330                                 } else {
3331                                   firstMove = TRUE;
3332                                 }
3333                             }
3334                         }
3335                     }
3336 #endif
3337                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3338                         /* Moves came from oldmoves or moves command
3339                            while we weren't doing anything else.
3340                            */
3341                         currentMove = forwardMostMove;
3342                         ClearHighlights();/*!!could figure this out*/
3343                         flipView = appData.flipView;
3344                         DrawPosition(TRUE, boards[currentMove]);
3345                         DisplayBothClocks();
3346                         snprintf(str, MSG_SIZ, "%s vs. %s",
3347                                 gameInfo.white, gameInfo.black);
3348                         DisplayTitle(str);
3349                         gameMode = IcsIdle;
3350                     } else {
3351                         /* Moves were history of an active game */
3352                         if (gameInfo.resultDetails != NULL) {
3353                             free(gameInfo.resultDetails);
3354                             gameInfo.resultDetails = NULL;
3355                         }
3356                     }
3357                     HistorySet(parseList, backwardMostMove,
3358                                forwardMostMove, currentMove-1);
3359                     DisplayMove(currentMove - 1);
3360                     if (started == STARTED_MOVES) next_out = i;
3361                     started = STARTED_NONE;
3362                     ics_getting_history = H_FALSE;
3363                     break;
3364
3365                   case STARTED_OBSERVE:
3366                     started = STARTED_NONE;
3367                     SendToICS(ics_prefix);
3368                     SendToICS("refresh\n");
3369                     break;
3370
3371                   default:
3372                     break;
3373                 }
3374                 if(bookHit) { // [HGM] book: simulate book reply
3375                     static char bookMove[MSG_SIZ]; // a bit generous?
3376
3377                     programStats.nodes = programStats.depth = programStats.time =
3378                     programStats.score = programStats.got_only_move = 0;
3379                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3380
3381                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3382                     strcat(bookMove, bookHit);
3383                     HandleMachineMove(bookMove, &first);
3384                 }
3385                 continue;
3386             }
3387
3388             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3389                  started == STARTED_HOLDINGS ||
3390                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3391                 /* Accumulate characters in move list or board */
3392                 parse[parse_pos++] = buf[i];
3393             }
3394
3395             /* Start of game messages.  Mostly we detect start of game
3396                when the first board image arrives.  On some versions
3397                of the ICS, though, we need to do a "refresh" after starting
3398                to observe in order to get the current board right away. */
3399             if (looking_at(buf, &i, "Adding game * to observation list")) {
3400                 started = STARTED_OBSERVE;
3401                 continue;
3402             }
3403
3404             /* Handle auto-observe */
3405             if (appData.autoObserve &&
3406                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3407                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3408                 char *player;
3409                 /* Choose the player that was highlighted, if any. */
3410                 if (star_match[0][0] == '\033' ||
3411                     star_match[1][0] != '\033') {
3412                     player = star_match[0];
3413                 } else {
3414                     player = star_match[2];
3415                 }
3416                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3417                         ics_prefix, StripHighlightAndTitle(player));
3418                 SendToICS(str);
3419
3420                 /* Save ratings from notify string */
3421                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3422                 player1Rating = string_to_rating(star_match[1]);
3423                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3424                 player2Rating = string_to_rating(star_match[3]);
3425
3426                 if (appData.debugMode)
3427                   fprintf(debugFP,
3428                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3429                           player1Name, player1Rating,
3430                           player2Name, player2Rating);
3431
3432                 continue;
3433             }
3434
3435             /* Deal with automatic examine mode after a game,
3436                and with IcsObserving -> IcsExamining transition */
3437             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3438                 looking_at(buf, &i, "has made you an examiner of game *")) {
3439
3440                 int gamenum = atoi(star_match[0]);
3441                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3442                     gamenum == ics_gamenum) {
3443                     /* We were already playing or observing this game;
3444                        no need to refetch history */
3445                     gameMode = IcsExamining;
3446                     if (pausing) {
3447                         pauseExamForwardMostMove = forwardMostMove;
3448                     } else if (currentMove < forwardMostMove) {
3449                         ForwardInner(forwardMostMove);
3450                     }
3451                 } else {
3452                     /* I don't think this case really can happen */
3453                     SendToICS(ics_prefix);
3454                     SendToICS("refresh\n");
3455                 }
3456                 continue;
3457             }
3458
3459             /* Error messages */
3460 //          if (ics_user_moved) {
3461             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3462                 if (looking_at(buf, &i, "Illegal move") ||
3463                     looking_at(buf, &i, "Not a legal move") ||
3464                     looking_at(buf, &i, "Your king is in check") ||
3465                     looking_at(buf, &i, "It isn't your turn") ||
3466                     looking_at(buf, &i, "It is not your move")) {
3467                     /* Illegal move */
3468                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3469                         currentMove = forwardMostMove-1;
3470                         DisplayMove(currentMove - 1); /* before DMError */
3471                         DrawPosition(FALSE, boards[currentMove]);
3472                         SwitchClocks(forwardMostMove-1); // [HGM] race
3473                         DisplayBothClocks();
3474                     }
3475                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3476                     ics_user_moved = 0;
3477                     continue;
3478                 }
3479             }
3480
3481             if (looking_at(buf, &i, "still have time") ||
3482                 looking_at(buf, &i, "not out of time") ||
3483                 looking_at(buf, &i, "either player is out of time") ||
3484                 looking_at(buf, &i, "has timeseal; checking")) {
3485                 /* We must have called his flag a little too soon */
3486                 whiteFlag = blackFlag = FALSE;
3487                 continue;
3488             }
3489
3490             if (looking_at(buf, &i, "added * seconds to") ||
3491                 looking_at(buf, &i, "seconds were added to")) {
3492                 /* Update the clocks */
3493                 SendToICS(ics_prefix);
3494                 SendToICS("refresh\n");
3495                 continue;
3496             }
3497
3498             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3499                 ics_clock_paused = TRUE;
3500                 StopClocks();
3501                 continue;
3502             }
3503
3504             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3505                 ics_clock_paused = FALSE;
3506                 StartClocks();
3507                 continue;
3508             }
3509
3510             /* Grab player ratings from the Creating: message.
3511                Note we have to check for the special case when
3512                the ICS inserts things like [white] or [black]. */
3513             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3514                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3515                 /* star_matches:
3516                    0    player 1 name (not necessarily white)
3517                    1    player 1 rating
3518                    2    empty, white, or black (IGNORED)
3519                    3    player 2 name (not necessarily black)
3520                    4    player 2 rating
3521
3522                    The names/ratings are sorted out when the game
3523                    actually starts (below).
3524                 */
3525                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3526                 player1Rating = string_to_rating(star_match[1]);
3527                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3528                 player2Rating = string_to_rating(star_match[4]);
3529
3530                 if (appData.debugMode)
3531                   fprintf(debugFP,
3532                           "Ratings from 'Creating:' %s %d, %s %d\n",
3533                           player1Name, player1Rating,
3534                           player2Name, player2Rating);
3535
3536                 continue;
3537             }
3538
3539             /* Improved generic start/end-of-game messages */
3540             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3541                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3542                 /* If tkind == 0: */
3543                 /* star_match[0] is the game number */
3544                 /*           [1] is the white player's name */
3545                 /*           [2] is the black player's name */
3546                 /* For end-of-game: */
3547                 /*           [3] is the reason for the game end */
3548                 /*           [4] is a PGN end game-token, preceded by " " */
3549                 /* For start-of-game: */
3550                 /*           [3] begins with "Creating" or "Continuing" */
3551                 /*           [4] is " *" or empty (don't care). */
3552                 int gamenum = atoi(star_match[0]);
3553                 char *whitename, *blackname, *why, *endtoken;
3554                 ChessMove endtype = EndOfFile;
3555
3556                 if (tkind == 0) {
3557                   whitename = star_match[1];
3558                   blackname = star_match[2];
3559                   why = star_match[3];
3560                   endtoken = star_match[4];
3561                 } else {
3562                   whitename = star_match[1];
3563                   blackname = star_match[3];
3564                   why = star_match[5];
3565                   endtoken = star_match[6];
3566                 }
3567
3568                 /* Game start messages */
3569                 if (strncmp(why, "Creating ", 9) == 0 ||
3570                     strncmp(why, "Continuing ", 11) == 0) {
3571                     gs_gamenum = gamenum;
3572                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3573                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3574 #if ZIPPY
3575                     if (appData.zippyPlay) {
3576                         ZippyGameStart(whitename, blackname);
3577                     }
3578 #endif /*ZIPPY*/
3579                     partnerBoardValid = FALSE; // [HGM] bughouse
3580                     continue;
3581                 }
3582
3583                 /* Game end messages */
3584                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3585                     ics_gamenum != gamenum) {
3586                     continue;
3587                 }
3588                 while (endtoken[0] == ' ') endtoken++;
3589                 switch (endtoken[0]) {
3590                   case '*':
3591                   default:
3592                     endtype = GameUnfinished;
3593                     break;
3594                   case '0':
3595                     endtype = BlackWins;
3596                     break;
3597                   case '1':
3598                     if (endtoken[1] == '/')
3599                       endtype = GameIsDrawn;
3600                     else
3601                       endtype = WhiteWins;
3602                     break;
3603                 }
3604                 GameEnds(endtype, why, GE_ICS);
3605 #if ZIPPY
3606                 if (appData.zippyPlay && first.initDone) {
3607                     ZippyGameEnd(endtype, why);
3608                     if (first.pr == NULL) {
3609                       /* Start the next process early so that we'll
3610                          be ready for the next challenge */
3611                       StartChessProgram(&first);
3612                     }
3613                     /* Send "new" early, in case this command takes
3614                        a long time to finish, so that we'll be ready
3615                        for the next challenge. */
3616                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3617                     Reset(TRUE, TRUE);
3618                 }
3619 #endif /*ZIPPY*/
3620                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3621                 continue;
3622             }
3623
3624             if (looking_at(buf, &i, "Removing game * from observation") ||
3625                 looking_at(buf, &i, "no longer observing game *") ||
3626                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3627                 if (gameMode == IcsObserving &&
3628                     atoi(star_match[0]) == ics_gamenum)
3629                   {
3630                       /* icsEngineAnalyze */
3631                       if (appData.icsEngineAnalyze) {
3632                             ExitAnalyzeMode();
3633                             ModeHighlight();
3634                       }
3635                       StopClocks();
3636                       gameMode = IcsIdle;
3637                       ics_gamenum = -1;
3638                       ics_user_moved = FALSE;
3639                   }
3640                 continue;
3641             }
3642
3643             if (looking_at(buf, &i, "no longer examining game *")) {
3644                 if (gameMode == IcsExamining &&
3645                     atoi(star_match[0]) == ics_gamenum)
3646                   {
3647                       gameMode = IcsIdle;
3648                       ics_gamenum = -1;
3649                       ics_user_moved = FALSE;
3650                   }
3651                 continue;
3652             }
3653
3654             /* Advance leftover_start past any newlines we find,
3655                so only partial lines can get reparsed */
3656             if (looking_at(buf, &i, "\n")) {
3657                 prevColor = curColor;
3658                 if (curColor != ColorNormal) {
3659                     if (oldi > next_out) {
3660                         SendToPlayer(&buf[next_out], oldi - next_out);
3661                         next_out = oldi;
3662                     }
3663                     Colorize(ColorNormal, FALSE);
3664                     curColor = ColorNormal;
3665                 }
3666                 if (started == STARTED_BOARD) {
3667                     started = STARTED_NONE;
3668                     parse[parse_pos] = NULLCHAR;
3669                     ParseBoard12(parse);
3670                     ics_user_moved = 0;
3671
3672                     /* Send premove here */
3673                     if (appData.premove) {
3674                       char str[MSG_SIZ];
3675                       if (currentMove == 0 &&
3676                           gameMode == IcsPlayingWhite &&
3677                           appData.premoveWhite) {
3678                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3679                         if (appData.debugMode)
3680                           fprintf(debugFP, "Sending premove:\n");
3681                         SendToICS(str);
3682                       } else if (currentMove == 1 &&
3683                                  gameMode == IcsPlayingBlack &&
3684                                  appData.premoveBlack) {
3685                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3686                         if (appData.debugMode)
3687                           fprintf(debugFP, "Sending premove:\n");
3688                         SendToICS(str);
3689                       } else if (gotPremove) {
3690                         gotPremove = 0;
3691                         ClearPremoveHighlights();
3692                         if (appData.debugMode)
3693                           fprintf(debugFP, "Sending premove:\n");
3694                           UserMoveEvent(premoveFromX, premoveFromY,
3695                                         premoveToX, premoveToY,
3696                                         premovePromoChar);
3697                       }
3698                     }
3699
3700                     /* Usually suppress following prompt */
3701                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3702                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3703                         if (looking_at(buf, &i, "*% ")) {
3704                             savingComment = FALSE;
3705                             suppressKibitz = 0;
3706                         }
3707                     }
3708                     next_out = i;
3709                 } else if (started == STARTED_HOLDINGS) {
3710                     int gamenum;
3711                     char new_piece[MSG_SIZ];
3712                     started = STARTED_NONE;
3713                     parse[parse_pos] = NULLCHAR;
3714                     if (appData.debugMode)
3715                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3716                                                         parse, currentMove);
3717                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3718                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3719                         if (gameInfo.variant == VariantNormal) {
3720                           /* [HGM] We seem to switch variant during a game!
3721                            * Presumably no holdings were displayed, so we have
3722                            * to move the position two files to the right to
3723                            * create room for them!
3724                            */
3725                           VariantClass newVariant;
3726                           switch(gameInfo.boardWidth) { // base guess on board width
3727                                 case 9:  newVariant = VariantShogi; break;
3728                                 case 10: newVariant = VariantGreat; break;
3729                                 default: newVariant = VariantCrazyhouse; break;
3730                           }
3731                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3732                           /* Get a move list just to see the header, which
3733                              will tell us whether this is really bug or zh */
3734                           if (ics_getting_history == H_FALSE) {
3735                             ics_getting_history = H_REQUESTED;
3736                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3737                             SendToICS(str);
3738                           }
3739                         }
3740                         new_piece[0] = NULLCHAR;
3741                         sscanf(parse, "game %d white [%s black [%s <- %s",
3742                                &gamenum, white_holding, black_holding,
3743                                new_piece);
3744                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3745                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3746                         /* [HGM] copy holdings to board holdings area */
3747                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3748                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3749                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3750 #if ZIPPY
3751                         if (appData.zippyPlay && first.initDone) {
3752                             ZippyHoldings(white_holding, black_holding,
3753                                           new_piece);
3754                         }
3755 #endif /*ZIPPY*/
3756                         if (tinyLayout || smallLayout) {
3757                             char wh[16], bh[16];
3758                             PackHolding(wh, white_holding);
3759                             PackHolding(bh, black_holding);
3760                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3761                                     gameInfo.white, gameInfo.black);
3762                         } else {
3763                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3764                                     gameInfo.white, white_holding,
3765                                     gameInfo.black, black_holding);
3766                         }
3767                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3768                         DrawPosition(FALSE, boards[currentMove]);
3769                         DisplayTitle(str);
3770                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3771                         sscanf(parse, "game %d white [%s black [%s <- %s",
3772                                &gamenum, white_holding, black_holding,
3773                                new_piece);
3774                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3775                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3776                         /* [HGM] copy holdings to partner-board holdings area */
3777                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3778                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3779                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3780                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3781                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3782                       }
3783                     }
3784                     /* Suppress following prompt */
3785                     if (looking_at(buf, &i, "*% ")) {
3786                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3787                         savingComment = FALSE;
3788                         suppressKibitz = 0;
3789                     }
3790                     next_out = i;
3791                 }
3792                 continue;
3793             }
3794
3795             i++;                /* skip unparsed character and loop back */
3796         }
3797
3798         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3799 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3800 //          SendToPlayer(&buf[next_out], i - next_out);
3801             started != STARTED_HOLDINGS && leftover_start > next_out) {
3802             SendToPlayer(&buf[next_out], leftover_start - next_out);
3803             next_out = i;
3804         }
3805
3806         leftover_len = buf_len - leftover_start;
3807         /* if buffer ends with something we couldn't parse,
3808            reparse it after appending the next read */
3809
3810     } else if (count == 0) {
3811         RemoveInputSource(isr);
3812         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3813     } else {
3814         DisplayFatalError(_("Error reading from ICS"), error, 1);
3815     }
3816 }
3817
3818
3819 /* Board style 12 looks like this:
3820
3821    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3822
3823  * The "<12> " is stripped before it gets to this routine.  The two
3824  * trailing 0's (flip state and clock ticking) are later addition, and
3825  * some chess servers may not have them, or may have only the first.
3826  * Additional trailing fields may be added in the future.
3827  */
3828
3829 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3830
3831 #define RELATION_OBSERVING_PLAYED    0
3832 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3833 #define RELATION_PLAYING_MYMOVE      1
3834 #define RELATION_PLAYING_NOTMYMOVE  -1
3835 #define RELATION_EXAMINING           2
3836 #define RELATION_ISOLATED_BOARD     -3
3837 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3838
3839 void
3840 ParseBoard12(string)
3841      char *string;
3842 {
3843     GameMode newGameMode;
3844     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3845     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3846     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3847     char to_play, board_chars[200];
3848     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3849     char black[32], white[32];
3850     Board board;
3851     int prevMove = currentMove;
3852     int ticking = 2;
3853     ChessMove moveType;
3854     int fromX, fromY, toX, toY;
3855     char promoChar;
3856     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3857     char *bookHit = NULL; // [HGM] book
3858     Boolean weird = FALSE, reqFlag = FALSE;
3859
3860     fromX = fromY = toX = toY = -1;
3861
3862     newGame = FALSE;
3863
3864     if (appData.debugMode)
3865       fprintf(debugFP, _("Parsing board: %s\n"), string);
3866
3867     move_str[0] = NULLCHAR;
3868     elapsed_time[0] = NULLCHAR;
3869     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3870         int  i = 0, j;
3871         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3872             if(string[i] == ' ') { ranks++; files = 0; }
3873             else files++;
3874             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3875             i++;
3876         }
3877         for(j = 0; j <i; j++) board_chars[j] = string[j];
3878         board_chars[i] = '\0';
3879         string += i + 1;
3880     }
3881     n = sscanf(string, PATTERN, &to_play, &double_push,
3882                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3883                &gamenum, white, black, &relation, &basetime, &increment,
3884                &white_stren, &black_stren, &white_time, &black_time,
3885                &moveNum, str, elapsed_time, move_str, &ics_flip,
3886                &ticking);
3887
3888     if (n < 21) {
3889         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3890         DisplayError(str, 0);
3891         return;
3892     }
3893
3894     /* Convert the move number to internal form */
3895     moveNum = (moveNum - 1) * 2;
3896     if (to_play == 'B') moveNum++;
3897     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3898       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3899                         0, 1);
3900       return;
3901     }
3902
3903     switch (relation) {
3904       case RELATION_OBSERVING_PLAYED:
3905       case RELATION_OBSERVING_STATIC:
3906         if (gamenum == -1) {
3907             /* Old ICC buglet */
3908             relation = RELATION_OBSERVING_STATIC;
3909         }
3910         newGameMode = IcsObserving;
3911         break;
3912       case RELATION_PLAYING_MYMOVE:
3913       case RELATION_PLAYING_NOTMYMOVE:
3914         newGameMode =
3915           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3916             IcsPlayingWhite : IcsPlayingBlack;
3917         break;
3918       case RELATION_EXAMINING:
3919         newGameMode = IcsExamining;
3920         break;
3921       case RELATION_ISOLATED_BOARD:
3922       default:
3923         /* Just display this board.  If user was doing something else,
3924            we will forget about it until the next board comes. */
3925         newGameMode = IcsIdle;
3926         break;
3927       case RELATION_STARTING_POSITION:
3928         newGameMode = gameMode;
3929         break;
3930     }
3931
3932     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3933          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3934       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3935       char *toSqr;
3936       for (k = 0; k < ranks; k++) {
3937         for (j = 0; j < files; j++)
3938           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3939         if(gameInfo.holdingsWidth > 1) {
3940              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3941              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3942         }
3943       }
3944       CopyBoard(partnerBoard, board);
3945       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3946         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3947         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3948       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3949       if(toSqr = strchr(str, '-')) {
3950         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3951         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3952       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3953       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3954       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3955       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3956       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3957       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3958                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3959       DisplayMessage(partnerStatus, "");
3960         partnerBoardValid = TRUE;
3961       return;
3962     }
3963
3964     /* Modify behavior for initial board display on move listing
3965        of wild games.
3966        */
3967     switch (ics_getting_history) {
3968       case H_FALSE:
3969       case H_REQUESTED:
3970         break;
3971       case H_GOT_REQ_HEADER:
3972       case H_GOT_UNREQ_HEADER:
3973         /* This is the initial position of the current game */
3974         gamenum = ics_gamenum;
3975         moveNum = 0;            /* old ICS bug workaround */
3976         if (to_play == 'B') {
3977           startedFromSetupPosition = TRUE;
3978           blackPlaysFirst = TRUE;
3979           moveNum = 1;
3980           if (forwardMostMove == 0) forwardMostMove = 1;
3981           if (backwardMostMove == 0) backwardMostMove = 1;
3982           if (currentMove == 0) currentMove = 1;
3983         }
3984         newGameMode = gameMode;
3985         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3986         break;
3987       case H_GOT_UNWANTED_HEADER:
3988         /* This is an initial board that we don't want */
3989         return;
3990       case H_GETTING_MOVES:
3991         /* Should not happen */
3992         DisplayError(_("Error gathering move list: extra board"), 0);
3993         ics_getting_history = H_FALSE;
3994         return;
3995     }
3996
3997    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
3998                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
3999      /* [HGM] We seem to have switched variant unexpectedly
4000       * Try to guess new variant from board size
4001       */
4002           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4003           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4004           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4005           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4006           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4007           if(!weird) newVariant = VariantNormal;
4008           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4009           /* Get a move list just to see the header, which
4010              will tell us whether this is really bug or zh */
4011           if (ics_getting_history == H_FALSE) {
4012             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4013             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4014             SendToICS(str);
4015           }
4016     }
4017
4018     /* Take action if this is the first board of a new game, or of a
4019        different game than is currently being displayed.  */
4020     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4021         relation == RELATION_ISOLATED_BOARD) {
4022
4023         /* Forget the old game and get the history (if any) of the new one */
4024         if (gameMode != BeginningOfGame) {
4025           Reset(TRUE, TRUE);
4026         }
4027         newGame = TRUE;
4028         if (appData.autoRaiseBoard) BoardToTop();
4029         prevMove = -3;
4030         if (gamenum == -1) {
4031             newGameMode = IcsIdle;
4032         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4033                    appData.getMoveList && !reqFlag) {
4034             /* Need to get game history */
4035             ics_getting_history = H_REQUESTED;
4036             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4037             SendToICS(str);
4038         }
4039
4040         /* Initially flip the board to have black on the bottom if playing
4041            black or if the ICS flip flag is set, but let the user change
4042            it with the Flip View button. */
4043         flipView = appData.autoFlipView ?
4044           (newGameMode == IcsPlayingBlack) || ics_flip :
4045           appData.flipView;
4046
4047         /* Done with values from previous mode; copy in new ones */
4048         gameMode = newGameMode;
4049         ModeHighlight();
4050         ics_gamenum = gamenum;
4051         if (gamenum == gs_gamenum) {
4052             int klen = strlen(gs_kind);
4053             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4054             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4055             gameInfo.event = StrSave(str);
4056         } else {
4057             gameInfo.event = StrSave("ICS game");
4058         }
4059         gameInfo.site = StrSave(appData.icsHost);
4060         gameInfo.date = PGNDate();
4061         gameInfo.round = StrSave("-");
4062         gameInfo.white = StrSave(white);
4063         gameInfo.black = StrSave(black);
4064         timeControl = basetime * 60 * 1000;
4065         timeControl_2 = 0;
4066         timeIncrement = increment * 1000;
4067         movesPerSession = 0;
4068         gameInfo.timeControl = TimeControlTagValue();
4069         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4070   if (appData.debugMode) {
4071     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4072     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4073     setbuf(debugFP, NULL);
4074   }
4075
4076         gameInfo.outOfBook = NULL;
4077
4078         /* Do we have the ratings? */
4079         if (strcmp(player1Name, white) == 0 &&
4080             strcmp(player2Name, black) == 0) {
4081             if (appData.debugMode)
4082               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4083                       player1Rating, player2Rating);
4084             gameInfo.whiteRating = player1Rating;
4085             gameInfo.blackRating = player2Rating;
4086         } else if (strcmp(player2Name, white) == 0 &&
4087                    strcmp(player1Name, black) == 0) {
4088             if (appData.debugMode)
4089               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4090                       player2Rating, player1Rating);
4091             gameInfo.whiteRating = player2Rating;
4092             gameInfo.blackRating = player1Rating;
4093         }
4094         player1Name[0] = player2Name[0] = NULLCHAR;
4095
4096         /* Silence shouts if requested */
4097         if (appData.quietPlay &&
4098             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4099             SendToICS(ics_prefix);
4100             SendToICS("set shout 0\n");
4101         }
4102     }
4103
4104     /* Deal with midgame name changes */
4105     if (!newGame) {
4106         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4107             if (gameInfo.white) free(gameInfo.white);
4108             gameInfo.white = StrSave(white);
4109         }
4110         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4111             if (gameInfo.black) free(gameInfo.black);
4112             gameInfo.black = StrSave(black);
4113         }
4114     }
4115
4116     /* Throw away game result if anything actually changes in examine mode */
4117     if (gameMode == IcsExamining && !newGame) {
4118         gameInfo.result = GameUnfinished;
4119         if (gameInfo.resultDetails != NULL) {
4120             free(gameInfo.resultDetails);
4121             gameInfo.resultDetails = NULL;
4122         }
4123     }
4124
4125     /* In pausing && IcsExamining mode, we ignore boards coming
4126        in if they are in a different variation than we are. */
4127     if (pauseExamInvalid) return;
4128     if (pausing && gameMode == IcsExamining) {
4129         if (moveNum <= pauseExamForwardMostMove) {
4130             pauseExamInvalid = TRUE;
4131             forwardMostMove = pauseExamForwardMostMove;
4132             return;
4133         }
4134     }
4135
4136   if (appData.debugMode) {
4137     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4138   }
4139     /* Parse the board */
4140     for (k = 0; k < ranks; k++) {
4141       for (j = 0; j < files; j++)
4142         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4143       if(gameInfo.holdingsWidth > 1) {
4144            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4145            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4146       }
4147     }
4148     CopyBoard(boards[moveNum], board);
4149     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4150     if (moveNum == 0) {
4151         startedFromSetupPosition =
4152           !CompareBoards(board, initialPosition);
4153         if(startedFromSetupPosition)
4154             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4155     }
4156
4157     /* [HGM] Set castling rights. Take the outermost Rooks,
4158        to make it also work for FRC opening positions. Note that board12
4159        is really defective for later FRC positions, as it has no way to
4160        indicate which Rook can castle if they are on the same side of King.
4161        For the initial position we grant rights to the outermost Rooks,
4162        and remember thos rights, and we then copy them on positions
4163        later in an FRC game. This means WB might not recognize castlings with
4164        Rooks that have moved back to their original position as illegal,
4165        but in ICS mode that is not its job anyway.
4166     */
4167     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4168     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4169
4170         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4171             if(board[0][i] == WhiteRook) j = i;
4172         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4173         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4174             if(board[0][i] == WhiteRook) j = i;
4175         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4176         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4177             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4178         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4179         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4180             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4181         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4182
4183         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4184         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4185             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4186         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4187             if(board[BOARD_HEIGHT-1][k] == bKing)
4188                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4189         if(gameInfo.variant == VariantTwoKings) {
4190             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4191             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4192             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4193         }
4194     } else { int r;
4195         r = boards[moveNum][CASTLING][0] = initialRights[0];
4196         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4197         r = boards[moveNum][CASTLING][1] = initialRights[1];
4198         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4199         r = boards[moveNum][CASTLING][3] = initialRights[3];
4200         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4201         r = boards[moveNum][CASTLING][4] = initialRights[4];
4202         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4203         /* wildcastle kludge: always assume King has rights */
4204         r = boards[moveNum][CASTLING][2] = initialRights[2];
4205         r = boards[moveNum][CASTLING][5] = initialRights[5];
4206     }
4207     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4208     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4209
4210
4211     if (ics_getting_history == H_GOT_REQ_HEADER ||
4212         ics_getting_history == H_GOT_UNREQ_HEADER) {
4213         /* This was an initial position from a move list, not
4214            the current position */
4215         return;
4216     }
4217
4218     /* Update currentMove and known move number limits */
4219     newMove = newGame || moveNum > forwardMostMove;
4220
4221     if (newGame) {
4222         forwardMostMove = backwardMostMove = currentMove = moveNum;
4223         if (gameMode == IcsExamining && moveNum == 0) {
4224           /* Workaround for ICS limitation: we are not told the wild
4225              type when starting to examine a game.  But if we ask for
4226              the move list, the move list header will tell us */
4227             ics_getting_history = H_REQUESTED;
4228             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4229             SendToICS(str);
4230         }
4231     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4232                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4233 #if ZIPPY
4234         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4235         /* [HGM] applied this also to an engine that is silently watching        */
4236         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4237             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4238             gameInfo.variant == currentlyInitializedVariant) {
4239           takeback = forwardMostMove - moveNum;
4240           for (i = 0; i < takeback; i++) {
4241             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4242             SendToProgram("undo\n", &first);
4243           }
4244         }
4245 #endif
4246
4247         forwardMostMove = moveNum;
4248         if (!pausing || currentMove > forwardMostMove)
4249           currentMove = forwardMostMove;
4250     } else {
4251         /* New part of history that is not contiguous with old part */
4252         if (pausing && gameMode == IcsExamining) {
4253             pauseExamInvalid = TRUE;
4254             forwardMostMove = pauseExamForwardMostMove;
4255             return;
4256         }
4257         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4258 #if ZIPPY
4259             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4260                 // [HGM] when we will receive the move list we now request, it will be
4261                 // fed to the engine from the first move on. So if the engine is not
4262                 // in the initial position now, bring it there.
4263                 InitChessProgram(&first, 0);
4264             }
4265 #endif
4266             ics_getting_history = H_REQUESTED;
4267             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4268             SendToICS(str);
4269         }
4270         forwardMostMove = backwardMostMove = currentMove = moveNum;
4271     }
4272
4273     /* Update the clocks */
4274     if (strchr(elapsed_time, '.')) {
4275       /* Time is in ms */
4276       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4277       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4278     } else {
4279       /* Time is in seconds */
4280       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4281       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4282     }
4283
4284
4285 #if ZIPPY
4286     if (appData.zippyPlay && newGame &&
4287         gameMode != IcsObserving && gameMode != IcsIdle &&
4288         gameMode != IcsExamining)
4289       ZippyFirstBoard(moveNum, basetime, increment);
4290 #endif
4291
4292     /* Put the move on the move list, first converting
4293        to canonical algebraic form. */
4294     if (moveNum > 0) {
4295   if (appData.debugMode) {
4296     if (appData.debugMode) { int f = forwardMostMove;
4297         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4298                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4299                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4300     }
4301     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4302     fprintf(debugFP, "moveNum = %d\n", moveNum);
4303     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4304     setbuf(debugFP, NULL);
4305   }
4306         if (moveNum <= backwardMostMove) {
4307             /* We don't know what the board looked like before
4308                this move.  Punt. */
4309           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4310             strcat(parseList[moveNum - 1], " ");
4311             strcat(parseList[moveNum - 1], elapsed_time);
4312             moveList[moveNum - 1][0] = NULLCHAR;
4313         } else if (strcmp(move_str, "none") == 0) {
4314             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4315             /* Again, we don't know what the board looked like;
4316                this is really the start of the game. */
4317             parseList[moveNum - 1][0] = NULLCHAR;
4318             moveList[moveNum - 1][0] = NULLCHAR;
4319             backwardMostMove = moveNum;
4320             startedFromSetupPosition = TRUE;
4321             fromX = fromY = toX = toY = -1;
4322         } else {
4323           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4324           //                 So we parse the long-algebraic move string in stead of the SAN move
4325           int valid; char buf[MSG_SIZ], *prom;
4326
4327           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4328                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4329           // str looks something like "Q/a1-a2"; kill the slash
4330           if(str[1] == '/')
4331             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4332           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4333           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4334                 strcat(buf, prom); // long move lacks promo specification!
4335           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4336                 if(appData.debugMode)
4337                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4338                 safeStrCpy(move_str, buf, MSG_SIZ);
4339           }
4340           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4341                                 &fromX, &fromY, &toX, &toY, &promoChar)
4342                || ParseOneMove(buf, moveNum - 1, &moveType,
4343                                 &fromX, &fromY, &toX, &toY, &promoChar);
4344           // end of long SAN patch
4345           if (valid) {
4346             (void) CoordsToAlgebraic(boards[moveNum - 1],
4347                                      PosFlags(moveNum - 1),
4348                                      fromY, fromX, toY, toX, promoChar,
4349                                      parseList[moveNum-1]);
4350             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4351               case MT_NONE:
4352               case MT_STALEMATE:
4353               default:
4354                 break;
4355               case MT_CHECK:
4356                 if(gameInfo.variant != VariantShogi)
4357                     strcat(parseList[moveNum - 1], "+");
4358                 break;
4359               case MT_CHECKMATE:
4360               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4361                 strcat(parseList[moveNum - 1], "#");
4362                 break;
4363             }
4364             strcat(parseList[moveNum - 1], " ");
4365             strcat(parseList[moveNum - 1], elapsed_time);
4366             /* currentMoveString is set as a side-effect of ParseOneMove */
4367             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4368             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4369             strcat(moveList[moveNum - 1], "\n");
4370
4371             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4372               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4373                 ChessSquare old, new = boards[moveNum][k][j];
4374                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4375                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4376                   if(old == new) continue;
4377                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4378                   else if(new == WhiteWazir || new == BlackWazir) {
4379                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4380                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4381                       else boards[moveNum][k][j] = old; // preserve type of Gold
4382                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4383                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4384               }
4385           } else {
4386             /* Move from ICS was illegal!?  Punt. */
4387             if (appData.debugMode) {
4388               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4389               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4390             }
4391             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4392             strcat(parseList[moveNum - 1], " ");
4393             strcat(parseList[moveNum - 1], elapsed_time);
4394             moveList[moveNum - 1][0] = NULLCHAR;
4395             fromX = fromY = toX = toY = -1;
4396           }
4397         }
4398   if (appData.debugMode) {
4399     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4400     setbuf(debugFP, NULL);
4401   }
4402
4403 #if ZIPPY
4404         /* Send move to chess program (BEFORE animating it). */
4405         if (appData.zippyPlay && !newGame && newMove &&
4406            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4407
4408             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4409                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4410                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4411                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4412                             move_str);
4413                     DisplayError(str, 0);
4414                 } else {
4415                     if (first.sendTime) {
4416                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4417                     }
4418                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4419                     if (firstMove && !bookHit) {
4420                         firstMove = FALSE;
4421                         if (first.useColors) {
4422                           SendToProgram(gameMode == IcsPlayingWhite ?
4423                                         "white\ngo\n" :
4424                                         "black\ngo\n", &first);
4425                         } else {
4426                           SendToProgram("go\n", &first);
4427                         }
4428                         first.maybeThinking = TRUE;
4429                     }
4430                 }
4431             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4432               if (moveList[moveNum - 1][0] == NULLCHAR) {
4433                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4434                 DisplayError(str, 0);
4435               } else {
4436                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4437                 SendMoveToProgram(moveNum - 1, &first);
4438               }
4439             }
4440         }
4441 #endif
4442     }
4443
4444     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4445         /* If move comes from a remote source, animate it.  If it
4446            isn't remote, it will have already been animated. */
4447         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4448             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4449         }
4450         if (!pausing && appData.highlightLastMove) {
4451             SetHighlights(fromX, fromY, toX, toY);
4452         }
4453     }
4454
4455     /* Start the clocks */
4456     whiteFlag = blackFlag = FALSE;
4457     appData.clockMode = !(basetime == 0 && increment == 0);
4458     if (ticking == 0) {
4459       ics_clock_paused = TRUE;
4460       StopClocks();
4461     } else if (ticking == 1) {
4462       ics_clock_paused = FALSE;
4463     }
4464     if (gameMode == IcsIdle ||
4465         relation == RELATION_OBSERVING_STATIC ||
4466         relation == RELATION_EXAMINING ||
4467         ics_clock_paused)
4468       DisplayBothClocks();
4469     else
4470       StartClocks();
4471
4472     /* Display opponents and material strengths */
4473     if (gameInfo.variant != VariantBughouse &&
4474         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4475         if (tinyLayout || smallLayout) {
4476             if(gameInfo.variant == VariantNormal)
4477               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4478                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4479                     basetime, increment);
4480             else
4481               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4482                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4483                     basetime, increment, (int) gameInfo.variant);
4484         } else {
4485             if(gameInfo.variant == VariantNormal)
4486               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4487                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4488                     basetime, increment);
4489             else
4490               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment, VariantName(gameInfo.variant));
4493         }
4494         DisplayTitle(str);
4495   if (appData.debugMode) {
4496     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4497   }
4498     }
4499
4500
4501     /* Display the board */
4502     if (!pausing && !appData.noGUI) {
4503
4504       if (appData.premove)
4505           if (!gotPremove ||
4506              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4507              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4508               ClearPremoveHighlights();
4509
4510       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4511         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4512       DrawPosition(j, boards[currentMove]);
4513
4514       DisplayMove(moveNum - 1);
4515       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4516             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4517               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4518         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4519       }
4520     }
4521
4522     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4523 #if ZIPPY
4524     if(bookHit) { // [HGM] book: simulate book reply
4525         static char bookMove[MSG_SIZ]; // a bit generous?
4526
4527         programStats.nodes = programStats.depth = programStats.time =
4528         programStats.score = programStats.got_only_move = 0;
4529         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4530
4531         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4532         strcat(bookMove, bookHit);
4533         HandleMachineMove(bookMove, &first);
4534     }
4535 #endif
4536 }
4537
4538 void
4539 GetMoveListEvent()
4540 {
4541     char buf[MSG_SIZ];
4542     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4543         ics_getting_history = H_REQUESTED;
4544         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4545         SendToICS(buf);
4546     }
4547 }
4548
4549 void
4550 AnalysisPeriodicEvent(force)
4551      int force;
4552 {
4553     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4554          && !force) || !appData.periodicUpdates)
4555       return;
4556
4557     /* Send . command to Crafty to collect stats */
4558     SendToProgram(".\n", &first);
4559
4560     /* Don't send another until we get a response (this makes
4561        us stop sending to old Crafty's which don't understand
4562        the "." command (sending illegal cmds resets node count & time,
4563        which looks bad)) */
4564     programStats.ok_to_send = 0;
4565 }
4566
4567 void ics_update_width(new_width)
4568         int new_width;
4569 {
4570         ics_printf("set width %d\n", new_width);
4571 }
4572
4573 void
4574 SendMoveToProgram(moveNum, cps)
4575      int moveNum;
4576      ChessProgramState *cps;
4577 {
4578     char buf[MSG_SIZ];
4579
4580     if (cps->useUsermove) {
4581       SendToProgram("usermove ", cps);
4582     }
4583     if (cps->useSAN) {
4584       char *space;
4585       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4586         int len = space - parseList[moveNum];
4587         memcpy(buf, parseList[moveNum], len);
4588         buf[len++] = '\n';
4589         buf[len] = NULLCHAR;
4590       } else {
4591         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4592       }
4593       SendToProgram(buf, cps);
4594     } else {
4595       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4596         AlphaRank(moveList[moveNum], 4);
4597         SendToProgram(moveList[moveNum], cps);
4598         AlphaRank(moveList[moveNum], 4); // and back
4599       } else
4600       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4601        * the engine. It would be nice to have a better way to identify castle
4602        * moves here. */
4603       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4604                                                                          && cps->useOOCastle) {
4605         int fromX = moveList[moveNum][0] - AAA;
4606         int fromY = moveList[moveNum][1] - ONE;
4607         int toX = moveList[moveNum][2] - AAA;
4608         int toY = moveList[moveNum][3] - ONE;
4609         if((boards[moveNum][fromY][fromX] == WhiteKing
4610             && boards[moveNum][toY][toX] == WhiteRook)
4611            || (boards[moveNum][fromY][fromX] == BlackKing
4612                && boards[moveNum][toY][toX] == BlackRook)) {
4613           if(toX > fromX) SendToProgram("O-O\n", cps);
4614           else SendToProgram("O-O-O\n", cps);
4615         }
4616         else SendToProgram(moveList[moveNum], cps);
4617       }
4618       else SendToProgram(moveList[moveNum], cps);
4619       /* End of additions by Tord */
4620     }
4621
4622     /* [HGM] setting up the opening has brought engine in force mode! */
4623     /*       Send 'go' if we are in a mode where machine should play. */
4624     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4625         (gameMode == TwoMachinesPlay   ||
4626 #if ZIPPY
4627          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4628 #endif
4629          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4630         SendToProgram("go\n", cps);
4631   if (appData.debugMode) {
4632     fprintf(debugFP, "(extra)\n");
4633   }
4634     }
4635     setboardSpoiledMachineBlack = 0;
4636 }
4637
4638 void
4639 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4640      ChessMove moveType;
4641      int fromX, fromY, toX, toY;
4642      char promoChar;
4643 {
4644     char user_move[MSG_SIZ];
4645
4646     switch (moveType) {
4647       default:
4648         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4649                 (int)moveType, fromX, fromY, toX, toY);
4650         DisplayError(user_move + strlen("say "), 0);
4651         break;
4652       case WhiteKingSideCastle:
4653       case BlackKingSideCastle:
4654       case WhiteQueenSideCastleWild:
4655       case BlackQueenSideCastleWild:
4656       /* PUSH Fabien */
4657       case WhiteHSideCastleFR:
4658       case BlackHSideCastleFR:
4659       /* POP Fabien */
4660         snprintf(user_move, MSG_SIZ, "o-o\n");
4661         break;
4662       case WhiteQueenSideCastle:
4663       case BlackQueenSideCastle:
4664       case WhiteKingSideCastleWild:
4665       case BlackKingSideCastleWild:
4666       /* PUSH Fabien */
4667       case WhiteASideCastleFR:
4668       case BlackASideCastleFR:
4669       /* POP Fabien */
4670         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4671         break;
4672       case WhiteNonPromotion:
4673       case BlackNonPromotion:
4674         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4675         break;
4676       case WhitePromotion:
4677       case BlackPromotion:
4678         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4679           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4680                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4681                 PieceToChar(WhiteFerz));
4682         else if(gameInfo.variant == VariantGreat)
4683           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4684                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4685                 PieceToChar(WhiteMan));
4686         else
4687           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4688                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4689                 promoChar);
4690         break;
4691       case WhiteDrop:
4692       case BlackDrop:
4693       drop:
4694         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4695                  ToUpper(PieceToChar((ChessSquare) fromX)),
4696                  AAA + toX, ONE + toY);
4697         break;
4698       case IllegalMove:  /* could be a variant we don't quite understand */
4699         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4700       case NormalMove:
4701       case WhiteCapturesEnPassant:
4702       case BlackCapturesEnPassant:
4703         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4704                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4705         break;
4706     }
4707     SendToICS(user_move);
4708     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4709         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4710 }
4711
4712 void
4713 UploadGameEvent()
4714 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4715     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4716     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4717     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4718         DisplayError("You cannot do this while you are playing or observing", 0);
4719         return;
4720     }
4721     if(gameMode != IcsExamining) { // is this ever not the case?
4722         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4723
4724         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4725           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4726         } else { // on FICS we must first go to general examine mode
4727           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4728         }
4729         if(gameInfo.variant != VariantNormal) {
4730             // try figure out wild number, as xboard names are not always valid on ICS
4731             for(i=1; i<=36; i++) {
4732               snprintf(buf, MSG_SIZ, "wild/%d", i);
4733                 if(StringToVariant(buf) == gameInfo.variant) break;
4734             }
4735             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4736             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4737             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4738         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4739         SendToICS(ics_prefix);
4740         SendToICS(buf);
4741         if(startedFromSetupPosition || backwardMostMove != 0) {
4742           fen = PositionToFEN(backwardMostMove, NULL);
4743           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4744             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4745             SendToICS(buf);
4746           } else { // FICS: everything has to set by separate bsetup commands
4747             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4748             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4749             SendToICS(buf);
4750             if(!WhiteOnMove(backwardMostMove)) {
4751                 SendToICS("bsetup tomove black\n");
4752             }
4753             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4754             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4755             SendToICS(buf);
4756             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4757             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4758             SendToICS(buf);
4759             i = boards[backwardMostMove][EP_STATUS];
4760             if(i >= 0) { // set e.p.
4761               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4762                 SendToICS(buf);
4763             }
4764             bsetup++;
4765           }
4766         }
4767       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4768             SendToICS("bsetup done\n"); // switch to normal examining.
4769     }
4770     for(i = backwardMostMove; i<last; i++) {
4771         char buf[20];
4772         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4773         SendToICS(buf);
4774     }
4775     SendToICS(ics_prefix);
4776     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4777 }
4778
4779 void
4780 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4781      int rf, ff, rt, ft;
4782      char promoChar;
4783      char move[7];
4784 {
4785     if (rf == DROP_RANK) {
4786       sprintf(move, "%c@%c%c\n",
4787                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4788     } else {
4789         if (promoChar == 'x' || promoChar == NULLCHAR) {
4790           sprintf(move, "%c%c%c%c\n",
4791                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4792         } else {
4793             sprintf(move, "%c%c%c%c%c\n",
4794                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4795         }
4796     }
4797 }
4798
4799 void
4800 ProcessICSInitScript(f)
4801      FILE *f;
4802 {
4803     char buf[MSG_SIZ];
4804
4805     while (fgets(buf, MSG_SIZ, f)) {
4806         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4807     }
4808
4809     fclose(f);
4810 }
4811
4812
4813 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4814 void
4815 AlphaRank(char *move, int n)
4816 {
4817 //    char *p = move, c; int x, y;
4818
4819     if (appData.debugMode) {
4820         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4821     }
4822
4823     if(move[1]=='*' &&
4824        move[2]>='0' && move[2]<='9' &&
4825        move[3]>='a' && move[3]<='x'    ) {
4826         move[1] = '@';
4827         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4828         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4829     } else
4830     if(move[0]>='0' && move[0]<='9' &&
4831        move[1]>='a' && move[1]<='x' &&
4832        move[2]>='0' && move[2]<='9' &&
4833        move[3]>='a' && move[3]<='x'    ) {
4834         /* input move, Shogi -> normal */
4835         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4836         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4837         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4838         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4839     } else
4840     if(move[1]=='@' &&
4841        move[3]>='0' && move[3]<='9' &&
4842        move[2]>='a' && move[2]<='x'    ) {
4843         move[1] = '*';
4844         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4845         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4846     } else
4847     if(
4848        move[0]>='a' && move[0]<='x' &&
4849        move[3]>='0' && move[3]<='9' &&
4850        move[2]>='a' && move[2]<='x'    ) {
4851          /* output move, normal -> Shogi */
4852         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4853         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4854         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4855         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4856         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4857     }
4858     if (appData.debugMode) {
4859         fprintf(debugFP, "   out = '%s'\n", move);
4860     }
4861 }
4862
4863 char yy_textstr[8000];
4864
4865 /* Parser for moves from gnuchess, ICS, or user typein box */
4866 Boolean
4867 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4868      char *move;
4869      int moveNum;
4870      ChessMove *moveType;
4871      int *fromX, *fromY, *toX, *toY;
4872      char *promoChar;
4873 {
4874     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4875
4876     switch (*moveType) {
4877       case WhitePromotion:
4878       case BlackPromotion:
4879       case WhiteNonPromotion:
4880       case BlackNonPromotion:
4881       case NormalMove:
4882       case WhiteCapturesEnPassant:
4883       case BlackCapturesEnPassant:
4884       case WhiteKingSideCastle:
4885       case WhiteQueenSideCastle:
4886       case BlackKingSideCastle:
4887       case BlackQueenSideCastle:
4888       case WhiteKingSideCastleWild:
4889       case WhiteQueenSideCastleWild:
4890       case BlackKingSideCastleWild:
4891       case BlackQueenSideCastleWild:
4892       /* Code added by Tord: */
4893       case WhiteHSideCastleFR:
4894       case WhiteASideCastleFR:
4895       case BlackHSideCastleFR:
4896       case BlackASideCastleFR:
4897       /* End of code added by Tord */
4898       case IllegalMove:         /* bug or odd chess variant */
4899         *fromX = currentMoveString[0] - AAA;
4900         *fromY = currentMoveString[1] - ONE;
4901         *toX = currentMoveString[2] - AAA;
4902         *toY = currentMoveString[3] - ONE;
4903         *promoChar = currentMoveString[4];
4904         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4905             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4906     if (appData.debugMode) {
4907         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4908     }
4909             *fromX = *fromY = *toX = *toY = 0;
4910             return FALSE;
4911         }
4912         if (appData.testLegality) {
4913           return (*moveType != IllegalMove);
4914         } else {
4915           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4916                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4917         }
4918
4919       case WhiteDrop:
4920       case BlackDrop:
4921         *fromX = *moveType == WhiteDrop ?
4922           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4923           (int) CharToPiece(ToLower(currentMoveString[0]));
4924         *fromY = DROP_RANK;
4925         *toX = currentMoveString[2] - AAA;
4926         *toY = currentMoveString[3] - ONE;
4927         *promoChar = NULLCHAR;
4928         return TRUE;
4929
4930       case AmbiguousMove:
4931       case ImpossibleMove:
4932       case EndOfFile:
4933       case ElapsedTime:
4934       case Comment:
4935       case PGNTag:
4936       case NAG:
4937       case WhiteWins:
4938       case BlackWins:
4939       case GameIsDrawn:
4940       default:
4941     if (appData.debugMode) {
4942         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4943     }
4944         /* bug? */
4945         *fromX = *fromY = *toX = *toY = 0;
4946         *promoChar = NULLCHAR;
4947         return FALSE;
4948     }
4949 }
4950
4951
4952 void
4953 ParsePV(char *pv, Boolean storeComments)
4954 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4955   int fromX, fromY, toX, toY; char promoChar;
4956   ChessMove moveType;
4957   Boolean valid;
4958   int nr = 0;
4959
4960   endPV = forwardMostMove;
4961   do {
4962     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4963     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4964     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4965 if(appData.debugMode){
4966 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4967 }
4968     if(!valid && nr == 0 &&
4969        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4970         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4971         // Hande case where played move is different from leading PV move
4972         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4973         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4974         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4975         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4976           endPV += 2; // if position different, keep this
4977           moveList[endPV-1][0] = fromX + AAA;
4978           moveList[endPV-1][1] = fromY + ONE;
4979           moveList[endPV-1][2] = toX + AAA;
4980           moveList[endPV-1][3] = toY + ONE;
4981           parseList[endPV-1][0] = NULLCHAR;
4982           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4983         }
4984       }
4985     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4986     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4987     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4988     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4989         valid++; // allow comments in PV
4990         continue;
4991     }
4992     nr++;
4993     if(endPV+1 > framePtr) break; // no space, truncate
4994     if(!valid) break;
4995     endPV++;
4996     CopyBoard(boards[endPV], boards[endPV-1]);
4997     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4998     moveList[endPV-1][0] = fromX + AAA;
4999     moveList[endPV-1][1] = fromY + ONE;
5000     moveList[endPV-1][2] = toX + AAA;
5001     moveList[endPV-1][3] = toY + ONE;
5002     moveList[endPV-1][4] = promoChar;
5003     moveList[endPV-1][5] = NULLCHAR;
5004     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5005     if(storeComments)
5006         CoordsToAlgebraic(boards[endPV - 1],
5007                              PosFlags(endPV - 1),
5008                              fromY, fromX, toY, toX, promoChar,
5009                              parseList[endPV - 1]);
5010     else
5011         parseList[endPV-1][0] = NULLCHAR;
5012   } while(valid);
5013   currentMove = endPV;
5014   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5015   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5016                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5017   DrawPosition(TRUE, boards[currentMove]);
5018 }
5019
5020 static int lastX, lastY;
5021
5022 Boolean
5023 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5024 {
5025         int startPV;
5026         char *p;
5027
5028         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5029         lastX = x; lastY = y;
5030         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5031         startPV = index;
5032         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5033         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5034         index = startPV;
5035         do{ while(buf[index] && buf[index] != '\n') index++;
5036         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5037         buf[index] = 0;
5038         ParsePV(buf+startPV, FALSE);
5039         *start = startPV; *end = index-1;
5040         return TRUE;
5041 }
5042
5043 Boolean
5044 LoadPV(int x, int y)
5045 { // called on right mouse click to load PV
5046   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5047   lastX = x; lastY = y;
5048   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5049   return TRUE;
5050 }
5051
5052 void
5053 UnLoadPV()
5054 {
5055   if(endPV < 0) return;
5056   endPV = -1;
5057   currentMove = forwardMostMove;
5058   ClearPremoveHighlights();
5059   DrawPosition(TRUE, boards[currentMove]);
5060 }
5061
5062 void
5063 MovePV(int x, int y, int h)
5064 { // step through PV based on mouse coordinates (called on mouse move)
5065   int margin = h>>3, step = 0;
5066
5067   if(endPV < 0) return;
5068   // we must somehow check if right button is still down (might be released off board!)
5069   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5070   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5071   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5072   if(!step) return;
5073   lastX = x; lastY = y;
5074   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5075   currentMove += step;
5076   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5077   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5078                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5079   DrawPosition(FALSE, boards[currentMove]);
5080 }
5081
5082
5083 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5084 // All positions will have equal probability, but the current method will not provide a unique
5085 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5086 #define DARK 1
5087 #define LITE 2
5088 #define ANY 3
5089
5090 int squaresLeft[4];
5091 int piecesLeft[(int)BlackPawn];
5092 int seed, nrOfShuffles;
5093
5094 void GetPositionNumber()
5095 {       // sets global variable seed
5096         int i;
5097
5098         seed = appData.defaultFrcPosition;
5099         if(seed < 0) { // randomize based on time for negative FRC position numbers
5100                 for(i=0; i<50; i++) seed += random();
5101                 seed = random() ^ random() >> 8 ^ random() << 8;
5102                 if(seed<0) seed = -seed;
5103         }
5104 }
5105
5106 int put(Board board, int pieceType, int rank, int n, int shade)
5107 // put the piece on the (n-1)-th empty squares of the given shade
5108 {
5109         int i;
5110
5111         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5112                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5113                         board[rank][i] = (ChessSquare) pieceType;
5114                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5115                         squaresLeft[ANY]--;
5116                         piecesLeft[pieceType]--;
5117                         return i;
5118                 }
5119         }
5120         return -1;
5121 }
5122
5123
5124 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5125 // calculate where the next piece goes, (any empty square), and put it there
5126 {
5127         int i;
5128
5129         i = seed % squaresLeft[shade];
5130         nrOfShuffles *= squaresLeft[shade];
5131         seed /= squaresLeft[shade];
5132         put(board, pieceType, rank, i, shade);
5133 }
5134
5135 void AddTwoPieces(Board board, int pieceType, int rank)
5136 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5137 {
5138         int i, n=squaresLeft[ANY], j=n-1, k;
5139
5140         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5141         i = seed % k;  // pick one
5142         nrOfShuffles *= k;
5143         seed /= k;
5144         while(i >= j) i -= j--;
5145         j = n - 1 - j; i += j;
5146         put(board, pieceType, rank, j, ANY);
5147         put(board, pieceType, rank, i, ANY);
5148 }
5149
5150 void SetUpShuffle(Board board, int number)
5151 {
5152         int i, p, first=1;
5153
5154         GetPositionNumber(); nrOfShuffles = 1;
5155
5156         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5157         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5158         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5159
5160         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5161
5162         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5163             p = (int) board[0][i];
5164             if(p < (int) BlackPawn) piecesLeft[p] ++;
5165             board[0][i] = EmptySquare;
5166         }
5167
5168         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5169             // shuffles restricted to allow normal castling put KRR first
5170             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5171                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5172             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5173                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5174             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5175                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5176             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5177                 put(board, WhiteRook, 0, 0, ANY);
5178             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5179         }
5180
5181         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5182             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5183             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5184                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5185                 while(piecesLeft[p] >= 2) {
5186                     AddOnePiece(board, p, 0, LITE);
5187                     AddOnePiece(board, p, 0, DARK);
5188                 }
5189                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5190             }
5191
5192         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5193             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5194             // but we leave King and Rooks for last, to possibly obey FRC restriction
5195             if(p == (int)WhiteRook) continue;
5196             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5197             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5198         }
5199
5200         // now everything is placed, except perhaps King (Unicorn) and Rooks
5201
5202         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5203             // Last King gets castling rights
5204             while(piecesLeft[(int)WhiteUnicorn]) {
5205                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5206                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5207             }
5208
5209             while(piecesLeft[(int)WhiteKing]) {
5210                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5211                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5212             }
5213
5214
5215         } else {
5216             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5217             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5218         }
5219
5220         // Only Rooks can be left; simply place them all
5221         while(piecesLeft[(int)WhiteRook]) {
5222                 i = put(board, WhiteRook, 0, 0, ANY);
5223                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5224                         if(first) {
5225                                 first=0;
5226                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5227                         }
5228                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5229                 }
5230         }
5231         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5232             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5233         }
5234
5235         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5236 }
5237
5238 int SetCharTable( char *table, const char * map )
5239 /* [HGM] moved here from winboard.c because of its general usefulness */
5240 /*       Basically a safe strcpy that uses the last character as King */
5241 {
5242     int result = FALSE; int NrPieces;
5243
5244     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5245                     && NrPieces >= 12 && !(NrPieces&1)) {
5246         int i; /* [HGM] Accept even length from 12 to 34 */
5247
5248         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5249         for( i=0; i<NrPieces/2-1; i++ ) {
5250             table[i] = map[i];
5251             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5252         }
5253         table[(int) WhiteKing]  = map[NrPieces/2-1];
5254         table[(int) BlackKing]  = map[NrPieces-1];
5255
5256         result = TRUE;
5257     }
5258
5259     return result;
5260 }
5261
5262 void Prelude(Board board)
5263 {       // [HGM] superchess: random selection of exo-pieces
5264         int i, j, k; ChessSquare p;
5265         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5266
5267         GetPositionNumber(); // use FRC position number
5268
5269         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5270             SetCharTable(pieceToChar, appData.pieceToCharTable);
5271             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5272                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5273         }
5274
5275         j = seed%4;                 seed /= 4;
5276         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5277         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5278         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5279         j = seed%3 + (seed%3 >= j); seed /= 3;
5280         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5281         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5282         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5283         j = seed%3;                 seed /= 3;
5284         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5285         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5286         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5287         j = seed%2 + (seed%2 >= j); seed /= 2;
5288         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5289         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5290         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5291         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5292         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5293         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5294         put(board, exoPieces[0],    0, 0, ANY);
5295         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5296 }
5297
5298 void
5299 InitPosition(redraw)
5300      int redraw;
5301 {
5302     ChessSquare (* pieces)[BOARD_FILES];
5303     int i, j, pawnRow, overrule,
5304     oldx = gameInfo.boardWidth,
5305     oldy = gameInfo.boardHeight,
5306     oldh = gameInfo.holdingsWidth;
5307     static int oldv;
5308
5309     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5310
5311     /* [AS] Initialize pv info list [HGM] and game status */
5312     {
5313         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5314             pvInfoList[i].depth = 0;
5315             boards[i][EP_STATUS] = EP_NONE;
5316             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5317         }
5318
5319         initialRulePlies = 0; /* 50-move counter start */
5320
5321         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5322         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5323     }
5324
5325
5326     /* [HGM] logic here is completely changed. In stead of full positions */
5327     /* the initialized data only consist of the two backranks. The switch */
5328     /* selects which one we will use, which is than copied to the Board   */
5329     /* initialPosition, which for the rest is initialized by Pawns and    */
5330     /* empty squares. This initial position is then copied to boards[0],  */
5331     /* possibly after shuffling, so that it remains available.            */
5332
5333     gameInfo.holdingsWidth = 0; /* default board sizes */
5334     gameInfo.boardWidth    = 8;
5335     gameInfo.boardHeight   = 8;
5336     gameInfo.holdingsSize  = 0;
5337     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5338     for(i=0; i<BOARD_FILES-2; i++)
5339       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5340     initialPosition[EP_STATUS] = EP_NONE;
5341     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5342     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5343          SetCharTable(pieceNickName, appData.pieceNickNames);
5344     else SetCharTable(pieceNickName, "............");
5345     pieces = FIDEArray;
5346
5347     switch (gameInfo.variant) {
5348     case VariantFischeRandom:
5349       shuffleOpenings = TRUE;
5350     default:
5351       break;
5352     case VariantShatranj:
5353       pieces = ShatranjArray;
5354       nrCastlingRights = 0;
5355       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5356       break;
5357     case VariantMakruk:
5358       pieces = makrukArray;
5359       nrCastlingRights = 0;
5360       startedFromSetupPosition = TRUE;
5361       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5362       break;
5363     case VariantTwoKings:
5364       pieces = twoKingsArray;
5365       break;
5366     case VariantCapaRandom:
5367       shuffleOpenings = TRUE;
5368     case VariantCapablanca:
5369       pieces = CapablancaArray;
5370       gameInfo.boardWidth = 10;
5371       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5372       break;
5373     case VariantGothic:
5374       pieces = GothicArray;
5375       gameInfo.boardWidth = 10;
5376       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5377       break;
5378     case VariantSChess:
5379       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5380       gameInfo.holdingsSize = 7;
5381       break;
5382     case VariantJanus:
5383       pieces = JanusArray;
5384       gameInfo.boardWidth = 10;
5385       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5386       nrCastlingRights = 6;
5387         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5388         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5389         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5390         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5391         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5392         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5393       break;
5394     case VariantFalcon:
5395       pieces = FalconArray;
5396       gameInfo.boardWidth = 10;
5397       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5398       break;
5399     case VariantXiangqi:
5400       pieces = XiangqiArray;
5401       gameInfo.boardWidth  = 9;
5402       gameInfo.boardHeight = 10;
5403       nrCastlingRights = 0;
5404       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5405       break;
5406     case VariantShogi:
5407       pieces = ShogiArray;
5408       gameInfo.boardWidth  = 9;
5409       gameInfo.boardHeight = 9;
5410       gameInfo.holdingsSize = 7;
5411       nrCastlingRights = 0;
5412       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5413       break;
5414     case VariantCourier:
5415       pieces = CourierArray;
5416       gameInfo.boardWidth  = 12;
5417       nrCastlingRights = 0;
5418       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5419       break;
5420     case VariantKnightmate:
5421       pieces = KnightmateArray;
5422       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5423       break;
5424     case VariantSpartan:
5425       pieces = SpartanArray;
5426       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5427       break;
5428     case VariantFairy:
5429       pieces = fairyArray;
5430       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5431       break;
5432     case VariantGreat:
5433       pieces = GreatArray;
5434       gameInfo.boardWidth = 10;
5435       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5436       gameInfo.holdingsSize = 8;
5437       break;
5438     case VariantSuper:
5439       pieces = FIDEArray;
5440       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5441       gameInfo.holdingsSize = 8;
5442       startedFromSetupPosition = TRUE;
5443       break;
5444     case VariantCrazyhouse:
5445     case VariantBughouse:
5446       pieces = FIDEArray;
5447       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5448       gameInfo.holdingsSize = 5;
5449       break;
5450     case VariantWildCastle:
5451       pieces = FIDEArray;
5452       /* !!?shuffle with kings guaranteed to be on d or e file */
5453       shuffleOpenings = 1;
5454       break;
5455     case VariantNoCastle:
5456       pieces = FIDEArray;
5457       nrCastlingRights = 0;
5458       /* !!?unconstrained back-rank shuffle */
5459       shuffleOpenings = 1;
5460       break;
5461     }
5462
5463     overrule = 0;
5464     if(appData.NrFiles >= 0) {
5465         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5466         gameInfo.boardWidth = appData.NrFiles;
5467     }
5468     if(appData.NrRanks >= 0) {
5469         gameInfo.boardHeight = appData.NrRanks;
5470     }
5471     if(appData.holdingsSize >= 0) {
5472         i = appData.holdingsSize;
5473         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5474         gameInfo.holdingsSize = i;
5475     }
5476     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5477     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5478         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5479
5480     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5481     if(pawnRow < 1) pawnRow = 1;
5482     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5483
5484     /* User pieceToChar list overrules defaults */
5485     if(appData.pieceToCharTable != NULL)
5486         SetCharTable(pieceToChar, appData.pieceToCharTable);
5487
5488     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5489
5490         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5491             s = (ChessSquare) 0; /* account holding counts in guard band */
5492         for( i=0; i<BOARD_HEIGHT; i++ )
5493             initialPosition[i][j] = s;
5494
5495         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5496         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5497         initialPosition[pawnRow][j] = WhitePawn;
5498         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5499         if(gameInfo.variant == VariantXiangqi) {
5500             if(j&1) {
5501                 initialPosition[pawnRow][j] =
5502                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5503                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5504                    initialPosition[2][j] = WhiteCannon;
5505                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5506                 }
5507             }
5508         }
5509         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5510     }
5511     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5512
5513             j=BOARD_LEFT+1;
5514             initialPosition[1][j] = WhiteBishop;
5515             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5516             j=BOARD_RGHT-2;
5517             initialPosition[1][j] = WhiteRook;
5518             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5519     }
5520
5521     if( nrCastlingRights == -1) {
5522         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5523         /*       This sets default castling rights from none to normal corners   */
5524         /* Variants with other castling rights must set them themselves above    */
5525         nrCastlingRights = 6;
5526
5527         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5528         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5529         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5530         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5531         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5532         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5533      }
5534
5535      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5536      if(gameInfo.variant == VariantGreat) { // promotion commoners
5537         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5538         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5539         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5540         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5541      }
5542      if( gameInfo.variant == VariantSChess ) {
5543       initialPosition[1][0] = BlackMarshall;
5544       initialPosition[2][0] = BlackAngel;
5545       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5546       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5547       initialPosition[1][1] = initialPosition[2][1] = 
5548       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5549      }
5550   if (appData.debugMode) {
5551     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5552   }
5553     if(shuffleOpenings) {
5554         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5555         startedFromSetupPosition = TRUE;
5556     }
5557     if(startedFromPositionFile) {
5558       /* [HGM] loadPos: use PositionFile for every new game */
5559       CopyBoard(initialPosition, filePosition);
5560       for(i=0; i<nrCastlingRights; i++)
5561           initialRights[i] = filePosition[CASTLING][i];
5562       startedFromSetupPosition = TRUE;
5563     }
5564
5565     CopyBoard(boards[0], initialPosition);
5566
5567     if(oldx != gameInfo.boardWidth ||
5568        oldy != gameInfo.boardHeight ||
5569        oldv != gameInfo.variant ||
5570        oldh != gameInfo.holdingsWidth
5571                                          )
5572             InitDrawingSizes(-2 ,0);
5573
5574     oldv = gameInfo.variant;
5575     if (redraw)
5576       DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 SendBoard(cps, moveNum)
5581      ChessProgramState *cps;
5582      int moveNum;
5583 {
5584     char message[MSG_SIZ];
5585
5586     if (cps->useSetboard) {
5587       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5588       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5589       SendToProgram(message, cps);
5590       free(fen);
5591
5592     } else {
5593       ChessSquare *bp;
5594       int i, j;
5595       /* Kludge to set black to move, avoiding the troublesome and now
5596        * deprecated "black" command.
5597        */
5598       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5599         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5600
5601       SendToProgram("edit\n", cps);
5602       SendToProgram("#\n", cps);
5603       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5604         bp = &boards[moveNum][i][BOARD_LEFT];
5605         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5606           if ((int) *bp < (int) BlackPawn) {
5607             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5608                     AAA + j, ONE + i);
5609             if(message[0] == '+' || message[0] == '~') {
5610               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5611                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5612                         AAA + j, ONE + i);
5613             }
5614             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5615                 message[1] = BOARD_RGHT   - 1 - j + '1';
5616                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5617             }
5618             SendToProgram(message, cps);
5619           }
5620         }
5621       }
5622
5623       SendToProgram("c\n", cps);
5624       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5625         bp = &boards[moveNum][i][BOARD_LEFT];
5626         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5627           if (((int) *bp != (int) EmptySquare)
5628               && ((int) *bp >= (int) BlackPawn)) {
5629             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5630                     AAA + j, ONE + i);
5631             if(message[0] == '+' || message[0] == '~') {
5632               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5633                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5634                         AAA + j, ONE + i);
5635             }
5636             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5637                 message[1] = BOARD_RGHT   - 1 - j + '1';
5638                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5639             }
5640             SendToProgram(message, cps);
5641           }
5642         }
5643       }
5644
5645       SendToProgram(".\n", cps);
5646     }
5647     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5648 }
5649
5650 static int autoQueen; // [HGM] oneclick
5651
5652 int
5653 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5654 {
5655     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5656     /* [HGM] add Shogi promotions */
5657     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5658     ChessSquare piece;
5659     ChessMove moveType;
5660     Boolean premove;
5661
5662     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5663     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5664
5665     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5666       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5667         return FALSE;
5668
5669     piece = boards[currentMove][fromY][fromX];
5670     if(gameInfo.variant == VariantShogi) {
5671         promotionZoneSize = BOARD_HEIGHT/3;
5672         highestPromotingPiece = (int)WhiteFerz;
5673     } else if(gameInfo.variant == VariantMakruk) {
5674         promotionZoneSize = 3;
5675     }
5676
5677     // Treat Lance as Pawn when it is not representing Amazon
5678     if(gameInfo.variant != VariantSuper) {
5679         if(piece == WhiteLance) piece = WhitePawn; else
5680         if(piece == BlackLance) piece = BlackPawn;
5681     }
5682
5683     // next weed out all moves that do not touch the promotion zone at all
5684     if((int)piece >= BlackPawn) {
5685         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5686              return FALSE;
5687         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5688     } else {
5689         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5690            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5691     }
5692
5693     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5694
5695     // weed out mandatory Shogi promotions
5696     if(gameInfo.variant == VariantShogi) {
5697         if(piece >= BlackPawn) {
5698             if(toY == 0 && piece == BlackPawn ||
5699                toY == 0 && piece == BlackQueen ||
5700                toY <= 1 && piece == BlackKnight) {
5701                 *promoChoice = '+';
5702                 return FALSE;
5703             }
5704         } else {
5705             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5706                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5707                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5708                 *promoChoice = '+';
5709                 return FALSE;
5710             }
5711         }
5712     }
5713
5714     // weed out obviously illegal Pawn moves
5715     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5716         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5717         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5718         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5719         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5720         // note we are not allowed to test for valid (non-)capture, due to premove
5721     }
5722
5723     // we either have a choice what to promote to, or (in Shogi) whether to promote
5724     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5725         *promoChoice = PieceToChar(BlackFerz);  // no choice
5726         return FALSE;
5727     }
5728     // no sense asking what we must promote to if it is going to explode...
5729     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5730         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5731         return FALSE;
5732     }
5733     if(autoQueen) { // predetermined
5734         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5735              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5736         else *promoChoice = PieceToChar(BlackQueen);
5737         return FALSE;
5738     }
5739
5740     // suppress promotion popup on illegal moves that are not premoves
5741     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5742               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5743     if(appData.testLegality && !premove) {
5744         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5745                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5746         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5747             return FALSE;
5748     }
5749
5750     return TRUE;
5751 }
5752
5753 int
5754 InPalace(row, column)
5755      int row, column;
5756 {   /* [HGM] for Xiangqi */
5757     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5758          column < (BOARD_WIDTH + 4)/2 &&
5759          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5760     return FALSE;
5761 }
5762
5763 int
5764 PieceForSquare (x, y)
5765      int x;
5766      int y;
5767 {
5768   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5769      return -1;
5770   else
5771      return boards[currentMove][y][x];
5772 }
5773
5774 int
5775 OKToStartUserMove(x, y)
5776      int x, y;
5777 {
5778     ChessSquare from_piece;
5779     int white_piece;
5780
5781     if (matchMode) return FALSE;
5782     if (gameMode == EditPosition) return TRUE;
5783
5784     if (x >= 0 && y >= 0)
5785       from_piece = boards[currentMove][y][x];
5786     else
5787       from_piece = EmptySquare;
5788
5789     if (from_piece == EmptySquare) return FALSE;
5790
5791     white_piece = (int)from_piece >= (int)WhitePawn &&
5792       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5793
5794     switch (gameMode) {
5795       case PlayFromGameFile:
5796       case AnalyzeFile:
5797       case TwoMachinesPlay:
5798       case EndOfGame:
5799         return FALSE;
5800
5801       case IcsObserving:
5802       case IcsIdle:
5803         return FALSE;
5804
5805       case MachinePlaysWhite:
5806       case IcsPlayingBlack:
5807         if (appData.zippyPlay) return FALSE;
5808         if (white_piece) {
5809             DisplayMoveError(_("You are playing Black"));
5810             return FALSE;
5811         }
5812         break;
5813
5814       case MachinePlaysBlack:
5815       case IcsPlayingWhite:
5816         if (appData.zippyPlay) return FALSE;
5817         if (!white_piece) {
5818             DisplayMoveError(_("You are playing White"));
5819             return FALSE;
5820         }
5821         break;
5822
5823       case EditGame:
5824         if (!white_piece && WhiteOnMove(currentMove)) {
5825             DisplayMoveError(_("It is White's turn"));
5826             return FALSE;
5827         }
5828         if (white_piece && !WhiteOnMove(currentMove)) {
5829             DisplayMoveError(_("It is Black's turn"));
5830             return FALSE;
5831         }
5832         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5833             /* Editing correspondence game history */
5834             /* Could disallow this or prompt for confirmation */
5835             cmailOldMove = -1;
5836         }
5837         break;
5838
5839       case BeginningOfGame:
5840         if (appData.icsActive) return FALSE;
5841         if (!appData.noChessProgram) {
5842             if (!white_piece) {
5843                 DisplayMoveError(_("You are playing White"));
5844                 return FALSE;
5845             }
5846         }
5847         break;
5848
5849       case Training:
5850         if (!white_piece && WhiteOnMove(currentMove)) {
5851             DisplayMoveError(_("It is White's turn"));
5852             return FALSE;
5853         }
5854         if (white_piece && !WhiteOnMove(currentMove)) {
5855             DisplayMoveError(_("It is Black's turn"));
5856             return FALSE;
5857         }
5858         break;
5859
5860       default:
5861       case IcsExamining:
5862         break;
5863     }
5864     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5865         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5866         && gameMode != AnalyzeFile && gameMode != Training) {
5867         DisplayMoveError(_("Displayed position is not current"));
5868         return FALSE;
5869     }
5870     return TRUE;
5871 }
5872
5873 Boolean
5874 OnlyMove(int *x, int *y, Boolean captures) {
5875     DisambiguateClosure cl;
5876     if (appData.zippyPlay) return FALSE;
5877     switch(gameMode) {
5878       case MachinePlaysBlack:
5879       case IcsPlayingWhite:
5880       case BeginningOfGame:
5881         if(!WhiteOnMove(currentMove)) return FALSE;
5882         break;
5883       case MachinePlaysWhite:
5884       case IcsPlayingBlack:
5885         if(WhiteOnMove(currentMove)) return FALSE;
5886         break;
5887       case EditGame:
5888         break;
5889       default:
5890         return FALSE;
5891     }
5892     cl.pieceIn = EmptySquare;
5893     cl.rfIn = *y;
5894     cl.ffIn = *x;
5895     cl.rtIn = -1;
5896     cl.ftIn = -1;
5897     cl.promoCharIn = NULLCHAR;
5898     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5899     if( cl.kind == NormalMove ||
5900         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5901         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5902         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5903       fromX = cl.ff;
5904       fromY = cl.rf;
5905       *x = cl.ft;
5906       *y = cl.rt;
5907       return TRUE;
5908     }
5909     if(cl.kind != ImpossibleMove) return FALSE;
5910     cl.pieceIn = EmptySquare;
5911     cl.rfIn = -1;
5912     cl.ffIn = -1;
5913     cl.rtIn = *y;
5914     cl.ftIn = *x;
5915     cl.promoCharIn = NULLCHAR;
5916     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5917     if( cl.kind == NormalMove ||
5918         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5919         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5920         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5921       fromX = cl.ff;
5922       fromY = cl.rf;
5923       *x = cl.ft;
5924       *y = cl.rt;
5925       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5926       return TRUE;
5927     }
5928     return FALSE;
5929 }
5930
5931 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5932 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5933 int lastLoadGameUseList = FALSE;
5934 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5935 ChessMove lastLoadGameStart = EndOfFile;
5936
5937 void
5938 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5939      int fromX, fromY, toX, toY;
5940      int promoChar;
5941 {
5942     ChessMove moveType;
5943     ChessSquare pdown, pup;
5944
5945     /* Check if the user is playing in turn.  This is complicated because we
5946        let the user "pick up" a piece before it is his turn.  So the piece he
5947        tried to pick up may have been captured by the time he puts it down!
5948        Therefore we use the color the user is supposed to be playing in this
5949        test, not the color of the piece that is currently on the starting
5950        square---except in EditGame mode, where the user is playing both
5951        sides; fortunately there the capture race can't happen.  (It can
5952        now happen in IcsExamining mode, but that's just too bad.  The user
5953        will get a somewhat confusing message in that case.)
5954        */
5955
5956     switch (gameMode) {
5957       case PlayFromGameFile:
5958       case AnalyzeFile:
5959       case TwoMachinesPlay:
5960       case EndOfGame:
5961       case IcsObserving:
5962       case IcsIdle:
5963         /* We switched into a game mode where moves are not accepted,
5964            perhaps while the mouse button was down. */
5965         return;
5966
5967       case MachinePlaysWhite:
5968         /* User is moving for Black */
5969         if (WhiteOnMove(currentMove)) {
5970             DisplayMoveError(_("It is White's turn"));
5971             return;
5972         }
5973         break;
5974
5975       case MachinePlaysBlack:
5976         /* User is moving for White */
5977         if (!WhiteOnMove(currentMove)) {
5978             DisplayMoveError(_("It is Black's turn"));
5979             return;
5980         }
5981         break;
5982
5983       case EditGame:
5984       case IcsExamining:
5985       case BeginningOfGame:
5986       case AnalyzeMode:
5987       case Training:
5988         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5989             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5990             /* User is moving for Black */
5991             if (WhiteOnMove(currentMove)) {
5992                 DisplayMoveError(_("It is White's turn"));
5993                 return;
5994             }
5995         } else {
5996             /* User is moving for White */
5997             if (!WhiteOnMove(currentMove)) {
5998                 DisplayMoveError(_("It is Black's turn"));
5999                 return;
6000             }
6001         }
6002         break;
6003
6004       case IcsPlayingBlack:
6005         /* User is moving for Black */
6006         if (WhiteOnMove(currentMove)) {
6007             if (!appData.premove) {
6008                 DisplayMoveError(_("It is White's turn"));
6009             } else if (toX >= 0 && toY >= 0) {
6010                 premoveToX = toX;
6011                 premoveToY = toY;
6012                 premoveFromX = fromX;
6013                 premoveFromY = fromY;
6014                 premovePromoChar = promoChar;
6015                 gotPremove = 1;
6016                 if (appData.debugMode)
6017                     fprintf(debugFP, "Got premove: fromX %d,"
6018                             "fromY %d, toX %d, toY %d\n",
6019                             fromX, fromY, toX, toY);
6020             }
6021             return;
6022         }
6023         break;
6024
6025       case IcsPlayingWhite:
6026         /* User is moving for White */
6027         if (!WhiteOnMove(currentMove)) {
6028             if (!appData.premove) {
6029                 DisplayMoveError(_("It is Black's turn"));
6030             } else if (toX >= 0 && toY >= 0) {
6031                 premoveToX = toX;
6032                 premoveToY = toY;
6033                 premoveFromX = fromX;
6034                 premoveFromY = fromY;
6035                 premovePromoChar = promoChar;
6036                 gotPremove = 1;
6037                 if (appData.debugMode)
6038                     fprintf(debugFP, "Got premove: fromX %d,"
6039                             "fromY %d, toX %d, toY %d\n",
6040                             fromX, fromY, toX, toY);
6041             }
6042             return;
6043         }
6044         break;
6045
6046       default:
6047         break;
6048
6049       case EditPosition:
6050         /* EditPosition, empty square, or different color piece;
6051            click-click move is possible */
6052         if (toX == -2 || toY == -2) {
6053             boards[0][fromY][fromX] = EmptySquare;
6054             DrawPosition(FALSE, boards[currentMove]);
6055             return;
6056         } else if (toX >= 0 && toY >= 0) {
6057             boards[0][toY][toX] = boards[0][fromY][fromX];
6058             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6059                 if(boards[0][fromY][0] != EmptySquare) {
6060                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6061                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6062                 }
6063             } else
6064             if(fromX == BOARD_RGHT+1) {
6065                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6066                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6067                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6068                 }
6069             } else
6070             boards[0][fromY][fromX] = EmptySquare;
6071             DrawPosition(FALSE, boards[currentMove]);
6072             return;
6073         }
6074         return;
6075     }
6076
6077     if(toX < 0 || toY < 0) return;
6078     pdown = boards[currentMove][fromY][fromX];
6079     pup = boards[currentMove][toY][toX];
6080
6081     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6082     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
6083          if( pup != EmptySquare ) return;
6084          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6085            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6086                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6087            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6088            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6089            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6090            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6091          fromY = DROP_RANK;
6092     }
6093
6094     /* [HGM] always test for legality, to get promotion info */
6095     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6096                                          fromY, fromX, toY, toX, promoChar);
6097     /* [HGM] but possibly ignore an IllegalMove result */
6098     if (appData.testLegality) {
6099         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6100             DisplayMoveError(_("Illegal move"));
6101             return;
6102         }
6103     }
6104
6105     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6106 }
6107
6108 /* Common tail of UserMoveEvent and DropMenuEvent */
6109 int
6110 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6111      ChessMove moveType;
6112      int fromX, fromY, toX, toY;
6113      /*char*/int promoChar;
6114 {
6115     char *bookHit = 0;
6116
6117     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6118         // [HGM] superchess: suppress promotions to non-available piece
6119         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6120         if(WhiteOnMove(currentMove)) {
6121             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6122         } else {
6123             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6124         }
6125     }
6126
6127     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6128        move type in caller when we know the move is a legal promotion */
6129     if(moveType == NormalMove && promoChar)
6130         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6131
6132     /* [HGM] <popupFix> The following if has been moved here from
6133        UserMoveEvent(). Because it seemed to belong here (why not allow
6134        piece drops in training games?), and because it can only be
6135        performed after it is known to what we promote. */
6136     if (gameMode == Training) {
6137       /* compare the move played on the board to the next move in the
6138        * game. If they match, display the move and the opponent's response.
6139        * If they don't match, display an error message.
6140        */
6141       int saveAnimate;
6142       Board testBoard;
6143       CopyBoard(testBoard, boards[currentMove]);
6144       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6145
6146       if (CompareBoards(testBoard, boards[currentMove+1])) {
6147         ForwardInner(currentMove+1);
6148
6149         /* Autoplay the opponent's response.
6150          * if appData.animate was TRUE when Training mode was entered,
6151          * the response will be animated.
6152          */
6153         saveAnimate = appData.animate;
6154         appData.animate = animateTraining;
6155         ForwardInner(currentMove+1);
6156         appData.animate = saveAnimate;
6157
6158         /* check for the end of the game */
6159         if (currentMove >= forwardMostMove) {
6160           gameMode = PlayFromGameFile;
6161           ModeHighlight();
6162           SetTrainingModeOff();
6163           DisplayInformation(_("End of game"));
6164         }
6165       } else {
6166         DisplayError(_("Incorrect move"), 0);
6167       }
6168       return 1;
6169     }
6170
6171   /* Ok, now we know that the move is good, so we can kill
6172      the previous line in Analysis Mode */
6173   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6174                                 && currentMove < forwardMostMove) {
6175     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6176     else forwardMostMove = currentMove;
6177   }
6178
6179   /* If we need the chess program but it's dead, restart it */
6180   ResurrectChessProgram();
6181
6182   /* A user move restarts a paused game*/
6183   if (pausing)
6184     PauseEvent();
6185
6186   thinkOutput[0] = NULLCHAR;
6187
6188   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6189
6190   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6191
6192   if (gameMode == BeginningOfGame) {
6193     if (appData.noChessProgram) {
6194       gameMode = EditGame;
6195       SetGameInfo();
6196     } else {
6197       char buf[MSG_SIZ];
6198       gameMode = MachinePlaysBlack;
6199       StartClocks();
6200       SetGameInfo();
6201       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6202       DisplayTitle(buf);
6203       if (first.sendName) {
6204         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6205         SendToProgram(buf, &first);
6206       }
6207       StartClocks();
6208     }
6209     ModeHighlight();
6210   }
6211
6212   /* Relay move to ICS or chess engine */
6213   if (appData.icsActive) {
6214     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6215         gameMode == IcsExamining) {
6216       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6217         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6218         SendToICS("draw ");
6219         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6220       }
6221       // also send plain move, in case ICS does not understand atomic claims
6222       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6223       ics_user_moved = 1;
6224     }
6225   } else {
6226     if (first.sendTime && (gameMode == BeginningOfGame ||
6227                            gameMode == MachinePlaysWhite ||
6228                            gameMode == MachinePlaysBlack)) {
6229       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6230     }
6231     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6232          // [HGM] book: if program might be playing, let it use book
6233         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6234         first.maybeThinking = TRUE;
6235     } else SendMoveToProgram(forwardMostMove-1, &first);
6236     if (currentMove == cmailOldMove + 1) {
6237       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6238     }
6239   }
6240
6241   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6242
6243   switch (gameMode) {
6244   case EditGame:
6245     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6246     case MT_NONE:
6247     case MT_CHECK:
6248       break;
6249     case MT_CHECKMATE:
6250     case MT_STAINMATE:
6251       if (WhiteOnMove(currentMove)) {
6252         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6253       } else {
6254         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6255       }
6256       break;
6257     case MT_STALEMATE:
6258       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6259       break;
6260     }
6261     break;
6262
6263   case MachinePlaysBlack:
6264   case MachinePlaysWhite:
6265     /* disable certain menu options while machine is thinking */
6266     SetMachineThinkingEnables();
6267     break;
6268
6269   default:
6270     break;
6271   }
6272
6273   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6274
6275   if(bookHit) { // [HGM] book: simulate book reply
6276         static char bookMove[MSG_SIZ]; // a bit generous?
6277
6278         programStats.nodes = programStats.depth = programStats.time =
6279         programStats.score = programStats.got_only_move = 0;
6280         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6281
6282         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6283         strcat(bookMove, bookHit);
6284         HandleMachineMove(bookMove, &first);
6285   }
6286   return 1;
6287 }
6288
6289 void
6290 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6291      Board board;
6292      int flags;
6293      ChessMove kind;
6294      int rf, ff, rt, ft;
6295      VOIDSTAR closure;
6296 {
6297     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6298     Markers *m = (Markers *) closure;
6299     if(rf == fromY && ff == fromX)
6300         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6301                          || kind == WhiteCapturesEnPassant
6302                          || kind == BlackCapturesEnPassant);
6303     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6304 }
6305
6306 void
6307 MarkTargetSquares(int clear)
6308 {
6309   int x, y;
6310   if(!appData.markers || !appData.highlightDragging ||
6311      !appData.testLegality || gameMode == EditPosition) return;
6312   if(clear) {
6313     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6314   } else {
6315     int capt = 0;
6316     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6317     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6318       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6319       if(capt)
6320       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6321     }
6322   }
6323   DrawPosition(TRUE, NULL);
6324 }
6325
6326 int
6327 Explode(Board board, int fromX, int fromY, int toX, int toY)
6328 {
6329     if(gameInfo.variant == VariantAtomic &&
6330        (board[toY][toX] != EmptySquare ||                     // capture?
6331         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6332                          board[fromY][fromX] == BlackPawn   )
6333       )) {
6334         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6335         return TRUE;
6336     }
6337     return FALSE;
6338 }
6339
6340 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6341
6342 void LeftClick(ClickType clickType, int xPix, int yPix)
6343 {
6344     int x, y;
6345     Boolean saveAnimate;
6346     static int second = 0, promotionChoice = 0, dragging = 0;
6347     char promoChoice = NULLCHAR;
6348
6349     if(appData.seekGraph && appData.icsActive && loggedOn &&
6350         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6351         SeekGraphClick(clickType, xPix, yPix, 0);
6352         return;
6353     }
6354
6355     if (clickType == Press) ErrorPopDown();
6356     MarkTargetSquares(1);
6357
6358     x = EventToSquare(xPix, BOARD_WIDTH);
6359     y = EventToSquare(yPix, BOARD_HEIGHT);
6360     if (!flipView && y >= 0) {
6361         y = BOARD_HEIGHT - 1 - y;
6362     }
6363     if (flipView && x >= 0) {
6364         x = BOARD_WIDTH - 1 - x;
6365     }
6366
6367     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6368         if(clickType == Release) return; // ignore upclick of click-click destination
6369         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6370         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6371         if(gameInfo.holdingsWidth &&
6372                 (WhiteOnMove(currentMove)
6373                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6374                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6375             // click in right holdings, for determining promotion piece
6376             ChessSquare p = boards[currentMove][y][x];
6377             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6378             if(p != EmptySquare) {
6379                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6380                 fromX = fromY = -1;
6381                 return;
6382             }
6383         }
6384         DrawPosition(FALSE, boards[currentMove]);
6385         return;
6386     }
6387
6388     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6389     if(clickType == Press
6390             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6391               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6392               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6393         return;
6394
6395     autoQueen = appData.alwaysPromoteToQueen;
6396
6397     if (fromX == -1) {
6398       gatingPiece = EmptySquare;
6399       if (clickType != Press) {
6400         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6401             DragPieceEnd(xPix, yPix); dragging = 0;
6402             DrawPosition(FALSE, NULL);
6403         }
6404         return;
6405       }
6406       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6407             /* First square */
6408             if (OKToStartUserMove(x, y)) {
6409                 fromX = x;
6410                 fromY = y;
6411                 second = 0;
6412                 MarkTargetSquares(0);
6413                 DragPieceBegin(xPix, yPix); dragging = 1;
6414                 if (appData.highlightDragging) {
6415                     SetHighlights(x, y, -1, -1);
6416                 }
6417             }
6418             return;
6419         }
6420     }
6421
6422     /* fromX != -1 */
6423     if (clickType == Press && gameMode != EditPosition) {
6424         ChessSquare fromP;
6425         ChessSquare toP;
6426         int frc;
6427
6428         // ignore off-board to clicks
6429         if(y < 0 || x < 0) return;
6430
6431         /* Check if clicking again on the same color piece */
6432         fromP = boards[currentMove][fromY][fromX];
6433         toP = boards[currentMove][y][x];
6434         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6435         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6436              WhitePawn <= toP && toP <= WhiteKing &&
6437              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6438              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6439             (BlackPawn <= fromP && fromP <= BlackKing &&
6440              BlackPawn <= toP && toP <= BlackKing &&
6441              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6442              !(fromP == BlackKing && toP == BlackRook && frc))) {
6443             /* Clicked again on same color piece -- changed his mind */
6444             second = (x == fromX && y == fromY);
6445            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6446             if (appData.highlightDragging) {
6447                 SetHighlights(x, y, -1, -1);
6448             } else {
6449                 ClearHighlights();
6450             }
6451             if (OKToStartUserMove(x, y)) {
6452                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6453                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6454                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6455                  gatingPiece = boards[currentMove][fromY][fromX];
6456                 else gatingPiece = EmptySquare;
6457                 fromX = x;
6458                 fromY = y; dragging = 1;
6459                 MarkTargetSquares(0);
6460                 DragPieceBegin(xPix, yPix);
6461             }
6462            }
6463            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6464            second = FALSE; 
6465         }
6466         // ignore clicks on holdings
6467         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6468     }
6469
6470     if (clickType == Release && x == fromX && y == fromY) {
6471         DragPieceEnd(xPix, yPix); dragging = 0;
6472         if (appData.animateDragging) {
6473             /* Undo animation damage if any */
6474             DrawPosition(FALSE, NULL);
6475         }
6476         if (second) {
6477             /* Second up/down in same square; just abort move */
6478             second = 0;
6479             fromX = fromY = -1;
6480             gatingPiece = EmptySquare;
6481             ClearHighlights();
6482             gotPremove = 0;
6483             ClearPremoveHighlights();
6484         } else {
6485             /* First upclick in same square; start click-click mode */
6486             SetHighlights(x, y, -1, -1);
6487         }
6488         return;
6489     }
6490
6491     /* we now have a different from- and (possibly off-board) to-square */
6492     /* Completed move */
6493     toX = x;
6494     toY = y;
6495     saveAnimate = appData.animate;
6496     if (clickType == Press) {
6497         /* Finish clickclick move */
6498         if (appData.animate || appData.highlightLastMove) {
6499             SetHighlights(fromX, fromY, toX, toY);
6500         } else {
6501             ClearHighlights();
6502         }
6503     } else {
6504         /* Finish drag move */
6505         if (appData.highlightLastMove) {
6506             SetHighlights(fromX, fromY, toX, toY);
6507         } else {
6508             ClearHighlights();
6509         }
6510         DragPieceEnd(xPix, yPix); dragging = 0;
6511         /* Don't animate move and drag both */
6512         appData.animate = FALSE;
6513     }
6514
6515     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6516     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6517         ChessSquare piece = boards[currentMove][fromY][fromX];
6518         if(gameMode == EditPosition && piece != EmptySquare &&
6519            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6520             int n;
6521
6522             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6523                 n = PieceToNumber(piece - (int)BlackPawn);
6524                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6525                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6526                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6527             } else
6528             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6529                 n = PieceToNumber(piece);
6530                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6531                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6532                 boards[currentMove][n][BOARD_WIDTH-2]++;
6533             }
6534             boards[currentMove][fromY][fromX] = EmptySquare;
6535         }
6536         ClearHighlights();
6537         fromX = fromY = -1;
6538         DrawPosition(TRUE, boards[currentMove]);
6539         return;
6540     }
6541
6542     // off-board moves should not be highlighted
6543     if(x < 0 || y < 0) ClearHighlights();
6544
6545     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6546
6547     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6548         SetHighlights(fromX, fromY, toX, toY);
6549         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6550             // [HGM] super: promotion to captured piece selected from holdings
6551             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6552             promotionChoice = TRUE;
6553             // kludge follows to temporarily execute move on display, without promoting yet
6554             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6555             boards[currentMove][toY][toX] = p;
6556             DrawPosition(FALSE, boards[currentMove]);
6557             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6558             boards[currentMove][toY][toX] = q;
6559             DisplayMessage("Click in holdings to choose piece", "");
6560             return;
6561         }
6562         PromotionPopUp();
6563     } else {
6564         int oldMove = currentMove;
6565         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6566         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6567         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6568         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6569            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6570             DrawPosition(TRUE, boards[currentMove]);
6571         fromX = fromY = -1;
6572     }
6573     appData.animate = saveAnimate;
6574     if (appData.animate || appData.animateDragging) {
6575         /* Undo animation damage if needed */
6576         DrawPosition(FALSE, NULL);
6577     }
6578 }
6579
6580 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6581 {   // front-end-free part taken out of PieceMenuPopup
6582     int whichMenu; int xSqr, ySqr;
6583
6584     if(seekGraphUp) { // [HGM] seekgraph
6585         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6586         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6587         return -2;
6588     }
6589
6590     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6591          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6592         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6593         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6594         if(action == Press)   {
6595             originalFlip = flipView;
6596             flipView = !flipView; // temporarily flip board to see game from partners perspective
6597             DrawPosition(TRUE, partnerBoard);
6598             DisplayMessage(partnerStatus, "");
6599             partnerUp = TRUE;
6600         } else if(action == Release) {
6601             flipView = originalFlip;
6602             DrawPosition(TRUE, boards[currentMove]);
6603             partnerUp = FALSE;
6604         }
6605         return -2;
6606     }
6607
6608     xSqr = EventToSquare(x, BOARD_WIDTH);
6609     ySqr = EventToSquare(y, BOARD_HEIGHT);
6610     if (action == Release) UnLoadPV(); // [HGM] pv
6611     if (action != Press) return -2; // return code to be ignored
6612     switch (gameMode) {
6613       case IcsExamining:
6614         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6615       case EditPosition:
6616         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6617         if (xSqr < 0 || ySqr < 0) return -1;\r
6618         whichMenu = 0; // edit-position menu
6619         break;
6620       case IcsObserving:
6621         if(!appData.icsEngineAnalyze) return -1;
6622       case IcsPlayingWhite:
6623       case IcsPlayingBlack:
6624         if(!appData.zippyPlay) goto noZip;
6625       case AnalyzeMode:
6626       case AnalyzeFile:
6627       case MachinePlaysWhite:
6628       case MachinePlaysBlack:
6629       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6630         if (!appData.dropMenu) {
6631           LoadPV(x, y);
6632           return 2; // flag front-end to grab mouse events
6633         }
6634         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6635            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6636       case EditGame:
6637       noZip:
6638         if (xSqr < 0 || ySqr < 0) return -1;
6639         if (!appData.dropMenu || appData.testLegality &&
6640             gameInfo.variant != VariantBughouse &&
6641             gameInfo.variant != VariantCrazyhouse) return -1;
6642         whichMenu = 1; // drop menu
6643         break;
6644       default:
6645         return -1;
6646     }
6647
6648     if (((*fromX = xSqr) < 0) ||
6649         ((*fromY = ySqr) < 0)) {
6650         *fromX = *fromY = -1;
6651         return -1;
6652     }
6653     if (flipView)
6654       *fromX = BOARD_WIDTH - 1 - *fromX;
6655     else
6656       *fromY = BOARD_HEIGHT - 1 - *fromY;
6657
6658     return whichMenu;
6659 }
6660
6661 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6662 {
6663 //    char * hint = lastHint;
6664     FrontEndProgramStats stats;
6665
6666     stats.which = cps == &first ? 0 : 1;
6667     stats.depth = cpstats->depth;
6668     stats.nodes = cpstats->nodes;
6669     stats.score = cpstats->score;
6670     stats.time = cpstats->time;
6671     stats.pv = cpstats->movelist;
6672     stats.hint = lastHint;
6673     stats.an_move_index = 0;
6674     stats.an_move_count = 0;
6675
6676     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6677         stats.hint = cpstats->move_name;
6678         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6679         stats.an_move_count = cpstats->nr_moves;
6680     }
6681
6682     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
6683
6684     SetProgramStats( &stats );
6685 }
6686
6687 void
6688 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6689 {       // count all piece types
6690         int p, f, r;
6691         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6692         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6693         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6694                 p = board[r][f];
6695                 pCnt[p]++;
6696                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6697                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6698                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6699                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6700                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6701                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6702         }
6703 }
6704
6705 int
6706 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6707 {
6708         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6709         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6710
6711         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6712         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6713         if(myPawns == 2 && nMine == 3) // KPP
6714             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6715         if(myPawns == 1 && nMine == 2) // KP
6716             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6717         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6718             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6719         if(myPawns) return FALSE;
6720         if(pCnt[WhiteRook+side])
6721             return pCnt[BlackRook-side] ||
6722                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6723                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6724                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6725         if(pCnt[WhiteCannon+side]) {
6726             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6727             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6728         }
6729         if(pCnt[WhiteKnight+side])
6730             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6731         return FALSE;
6732 }
6733
6734 int
6735 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6736 {
6737         VariantClass v = gameInfo.variant;
6738
6739         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6740         if(v == VariantShatranj) return TRUE; // always winnable through baring
6741         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6742         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6743
6744         if(v == VariantXiangqi) {
6745                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6746
6747                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6748                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6749                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6750                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6751                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6752                 if(stale) // we have at least one last-rank P plus perhaps C
6753                     return majors // KPKX
6754                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6755                 else // KCA*E*
6756                     return pCnt[WhiteFerz+side] // KCAK
6757                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6758                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6759                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6760
6761         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6762                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6763
6764                 if(nMine == 1) return FALSE; // bare King
6765                 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
6766                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6767                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6768                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6769                 if(pCnt[WhiteKnight+side])
6770                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6771                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6772                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6773                 if(nBishops)
6774                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6775                 if(pCnt[WhiteAlfil+side])
6776                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6777                 if(pCnt[WhiteWazir+side])
6778                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6779         }
6780
6781         return TRUE;
6782 }
6783
6784 int
6785 Adjudicate(ChessProgramState *cps)
6786 {       // [HGM] some adjudications useful with buggy engines
6787         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6788         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6789         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6790         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6791         int k, count = 0; static int bare = 1;
6792         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6793         Boolean canAdjudicate = !appData.icsActive;
6794
6795         // most tests only when we understand the game, i.e. legality-checking on
6796             if( appData.testLegality )
6797             {   /* [HGM] Some more adjudications for obstinate engines */
6798                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6799                 static int moveCount = 6;
6800                 ChessMove result;
6801                 char *reason = NULL;
6802
6803                 /* Count what is on board. */
6804                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6805
6806                 /* Some material-based adjudications that have to be made before stalemate test */
6807                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6808                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6809                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6810                      if(canAdjudicate && appData.checkMates) {
6811                          if(engineOpponent)
6812                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6813                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6814                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6815                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6816                          return 1;
6817                      }
6818                 }
6819
6820                 /* Bare King in Shatranj (loses) or Losers (wins) */
6821                 if( nrW == 1 || nrB == 1) {
6822                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6823                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6824                      if(canAdjudicate && appData.checkMates) {
6825                          if(engineOpponent)
6826                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6827                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
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                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6841                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6842                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6843                             return 1;
6844                         }
6845                   }
6846                 } else bare = 1;
6847
6848
6849             // don't wait for engine to announce game end if we can judge ourselves
6850             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6851               case MT_CHECK:
6852                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6853                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6854                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6855                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6856                             checkCnt++;
6857                         if(checkCnt >= 2) {
6858                             reason = "Xboard adjudication: 3rd check";
6859                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6860                             break;
6861                         }
6862                     }
6863                 }
6864               case MT_NONE:
6865               default:
6866                 break;
6867               case MT_STALEMATE:
6868               case MT_STAINMATE:
6869                 reason = "Xboard adjudication: Stalemate";
6870                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6871                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6872                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6873                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6874                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6875                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6876                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6877                                                                         EP_CHECKMATE : EP_WINS);
6878                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6879                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6880                 }
6881                 break;
6882               case MT_CHECKMATE:
6883                 reason = "Xboard adjudication: Checkmate";
6884                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6885                 break;
6886             }
6887
6888                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6889                     case EP_STALEMATE:
6890                         result = GameIsDrawn; break;
6891                     case EP_CHECKMATE:
6892                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6893                     case EP_WINS:
6894                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6895                     default:
6896                         result = EndOfFile;
6897                 }
6898                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6899                     if(engineOpponent)
6900                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6901                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6902                     GameEnds( result, reason, GE_XBOARD );
6903                     return 1;
6904                 }
6905
6906                 /* Next absolutely insufficient mating material. */
6907                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6908                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6909                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6910
6911                      /* always flag draws, for judging claims */
6912                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6913
6914                      if(canAdjudicate && appData.materialDraws) {
6915                          /* but only adjudicate them if adjudication enabled */
6916                          if(engineOpponent) {
6917                            SendToProgram("force\n", engineOpponent); // suppress reply
6918                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6919                          }
6920                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6921                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6922                          return 1;
6923                      }
6924                 }
6925
6926                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6927                 if(gameInfo.variant == VariantXiangqi ?
6928                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6929                  : nrW + nrB == 4 &&
6930                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6931                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6932                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6933                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6934                    ) ) {
6935                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6936                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6937                           if(engineOpponent) {
6938                             SendToProgram("force\n", engineOpponent); // suppress reply
6939                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6940                           }
6941                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6942                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6943                           return 1;
6944                      }
6945                 } else moveCount = 6;
6946             }
6947         if (appData.debugMode) { int i;
6948             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6949                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6950                     appData.drawRepeats);
6951             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6952               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6953
6954         }
6955
6956         // Repetition draws and 50-move rule can be applied independently of legality testing
6957
6958                 /* Check for rep-draws */
6959                 count = 0;
6960                 for(k = forwardMostMove-2;
6961                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6962                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6963                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6964                     k-=2)
6965                 {   int rights=0;
6966                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6967                         /* compare castling rights */
6968                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6969                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6970                                 rights++; /* King lost rights, while rook still had them */
6971                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6972                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6973                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6974                                    rights++; /* but at least one rook lost them */
6975                         }
6976                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6977                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6978                                 rights++;
6979                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6980                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6981                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6982                                    rights++;
6983                         }
6984                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6985                             && appData.drawRepeats > 1) {
6986                              /* adjudicate after user-specified nr of repeats */
6987                              int result = GameIsDrawn;
6988                              char *details = "XBoard adjudication: repetition draw";
6989                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6990                                 // [HGM] xiangqi: check for forbidden perpetuals
6991                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6992                                 for(m=forwardMostMove; m>k; m-=2) {
6993                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6994                                         ourPerpetual = 0; // the current mover did not always check
6995                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6996                                         hisPerpetual = 0; // the opponent did not always check
6997                                 }
6998                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6999                                                                         ourPerpetual, hisPerpetual);
7000                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7001                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7002                                     details = "Xboard adjudication: perpetual checking";
7003                                 } else
7004                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7005                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7006                                 } else
7007                                 // Now check for perpetual chases
7008                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7009                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7010                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7011                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7012                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7013                                         details = "Xboard adjudication: perpetual chasing";
7014                                     } else
7015                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7016                                         break; // Abort repetition-checking loop.
7017                                 }
7018                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7019                              }
7020                              if(engineOpponent) {
7021                                SendToProgram("force\n", engineOpponent); // suppress reply
7022                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7023                              }
7024                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7025                              GameEnds( result, details, GE_XBOARD );
7026                              return 1;
7027                         }
7028                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7029                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7030                     }
7031                 }
7032
7033                 /* Now we test for 50-move draws. Determine ply count */
7034                 count = forwardMostMove;
7035                 /* look for last irreversble move */
7036                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7037                     count--;
7038                 /* if we hit starting position, add initial plies */
7039                 if( count == backwardMostMove )
7040                     count -= initialRulePlies;
7041                 count = forwardMostMove - count;
7042                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7043                         // adjust reversible move counter for checks in Xiangqi
7044                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7045                         if(i < backwardMostMove) i = backwardMostMove;
7046                         while(i <= forwardMostMove) {
7047                                 lastCheck = inCheck; // check evasion does not count
7048                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7049                                 if(inCheck || lastCheck) count--; // check does not count
7050                                 i++;
7051                         }
7052                 }
7053                 if( count >= 100)
7054                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7055                          /* this is used to judge if draw claims are legal */
7056                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7057                          if(engineOpponent) {
7058                            SendToProgram("force\n", engineOpponent); // suppress reply
7059                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7060                          }
7061                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7062                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7063                          return 1;
7064                 }
7065
7066                 /* if draw offer is pending, treat it as a draw claim
7067                  * when draw condition present, to allow engines a way to
7068                  * claim draws before making their move to avoid a race
7069                  * condition occurring after their move
7070                  */
7071                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7072                          char *p = NULL;
7073                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7074                              p = "Draw claim: 50-move rule";
7075                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7076                              p = "Draw claim: 3-fold repetition";
7077                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7078                              p = "Draw claim: insufficient mating material";
7079                          if( p != NULL && canAdjudicate) {
7080                              if(engineOpponent) {
7081                                SendToProgram("force\n", engineOpponent); // suppress reply
7082                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7083                              }
7084                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7085                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7086                              return 1;
7087                          }
7088                 }
7089
7090                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7091                     if(engineOpponent) {
7092                       SendToProgram("force\n", engineOpponent); // suppress reply
7093                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7094                     }
7095                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7096                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7097                     return 1;
7098                 }
7099         return 0;
7100 }
7101
7102 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7103 {   // [HGM] book: this routine intercepts moves to simulate book replies
7104     char *bookHit = NULL;
7105
7106     //first determine if the incoming move brings opponent into his book
7107     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7108         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7109     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7110     if(bookHit != NULL && !cps->bookSuspend) {
7111         // make sure opponent is not going to reply after receiving move to book position
7112         SendToProgram("force\n", cps);
7113         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7114     }
7115     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7116     // now arrange restart after book miss
7117     if(bookHit) {
7118         // after a book hit we never send 'go', and the code after the call to this routine
7119         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7120         char buf[MSG_SIZ];
7121         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7122         SendToProgram(buf, cps);
7123         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7124     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7125         SendToProgram("go\n", cps);
7126         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7127     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7128         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7129             SendToProgram("go\n", cps);
7130         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7131     }
7132     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7133 }
7134
7135 char *savedMessage;
7136 ChessProgramState *savedState;
7137 void DeferredBookMove(void)
7138 {
7139         if(savedState->lastPing != savedState->lastPong)
7140                     ScheduleDelayedEvent(DeferredBookMove, 10);
7141         else
7142         HandleMachineMove(savedMessage, savedState);
7143 }
7144
7145 void
7146 HandleMachineMove(message, cps)
7147      char *message;
7148      ChessProgramState *cps;
7149 {
7150     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7151     char realname[MSG_SIZ];
7152     int fromX, fromY, toX, toY;
7153     ChessMove moveType;
7154     char promoChar;
7155     char *p;
7156     int machineWhite;
7157     char *bookHit;
7158
7159     cps->userError = 0;
7160
7161 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7162     /*
7163      * Kludge to ignore BEL characters
7164      */
7165     while (*message == '\007') message++;
7166
7167     /*
7168      * [HGM] engine debug message: ignore lines starting with '#' character
7169      */
7170     if(cps->debug && *message == '#') return;
7171
7172     /*
7173      * Look for book output
7174      */
7175     if (cps == &first && bookRequested) {
7176         if (message[0] == '\t' || message[0] == ' ') {
7177             /* Part of the book output is here; append it */
7178             strcat(bookOutput, message);
7179             strcat(bookOutput, "  \n");
7180             return;
7181         } else if (bookOutput[0] != NULLCHAR) {
7182             /* All of book output has arrived; display it */
7183             char *p = bookOutput;
7184             while (*p != NULLCHAR) {
7185                 if (*p == '\t') *p = ' ';
7186                 p++;
7187             }
7188             DisplayInformation(bookOutput);
7189             bookRequested = FALSE;
7190             /* Fall through to parse the current output */
7191         }
7192     }
7193
7194     /*
7195      * Look for machine move.
7196      */
7197     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7198         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7199     {
7200         /* This method is only useful on engines that support ping */
7201         if (cps->lastPing != cps->lastPong) {
7202           if (gameMode == BeginningOfGame) {
7203             /* Extra move from before last new; ignore */
7204             if (appData.debugMode) {
7205                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7206             }
7207           } else {
7208             if (appData.debugMode) {
7209                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7210                         cps->which, gameMode);
7211             }
7212
7213             SendToProgram("undo\n", cps);
7214           }
7215           return;
7216         }
7217
7218         switch (gameMode) {
7219           case BeginningOfGame:
7220             /* Extra move from before last reset; ignore */
7221             if (appData.debugMode) {
7222                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7223             }
7224             return;
7225
7226           case EndOfGame:
7227           case IcsIdle:
7228           default:
7229             /* Extra move after we tried to stop.  The mode test is
7230                not a reliable way of detecting this problem, but it's
7231                the best we can do on engines that don't support ping.
7232             */
7233             if (appData.debugMode) {
7234                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7235                         cps->which, gameMode);
7236             }
7237             SendToProgram("undo\n", cps);
7238             return;
7239
7240           case MachinePlaysWhite:
7241           case IcsPlayingWhite:
7242             machineWhite = TRUE;
7243             break;
7244
7245           case MachinePlaysBlack:
7246           case IcsPlayingBlack:
7247             machineWhite = FALSE;
7248             break;
7249
7250           case TwoMachinesPlay:
7251             machineWhite = (cps->twoMachinesColor[0] == 'w');
7252             break;
7253         }
7254         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7255             if (appData.debugMode) {
7256                 fprintf(debugFP,
7257                         "Ignoring move out of turn by %s, gameMode %d"
7258                         ", forwardMost %d\n",
7259                         cps->which, gameMode, forwardMostMove);
7260             }
7261             return;
7262         }
7263
7264     if (appData.debugMode) { int f = forwardMostMove;
7265         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7266                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7267                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7268     }
7269         if(cps->alphaRank) AlphaRank(machineMove, 4);
7270         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7271                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7272             /* Machine move could not be parsed; ignore it. */
7273           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7274                     machineMove, cps->which);
7275             DisplayError(buf1, 0);
7276             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7277                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7278             if (gameMode == TwoMachinesPlay) {
7279               GameEnds(machineWhite ? BlackWins : WhiteWins,
7280                        buf1, GE_XBOARD);
7281             }
7282             return;
7283         }
7284
7285         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7286         /* So we have to redo legality test with true e.p. status here,  */
7287         /* to make sure an illegal e.p. capture does not slip through,   */
7288         /* to cause a forfeit on a justified illegal-move complaint      */
7289         /* of the opponent.                                              */
7290         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7291            ChessMove moveType;
7292            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7293                              fromY, fromX, toY, toX, promoChar);
7294             if (appData.debugMode) {
7295                 int i;
7296                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7297                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7298                 fprintf(debugFP, "castling rights\n");
7299             }
7300             if(moveType == IllegalMove) {
7301               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7302                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7303                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7304                            buf1, GE_XBOARD);
7305                 return;
7306            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7307            /* [HGM] Kludge to handle engines that send FRC-style castling
7308               when they shouldn't (like TSCP-Gothic) */
7309            switch(moveType) {
7310              case WhiteASideCastleFR:
7311              case BlackASideCastleFR:
7312                toX+=2;
7313                currentMoveString[2]++;
7314                break;
7315              case WhiteHSideCastleFR:
7316              case BlackHSideCastleFR:
7317                toX--;
7318                currentMoveString[2]--;
7319                break;
7320              default: ; // nothing to do, but suppresses warning of pedantic compilers
7321            }
7322         }
7323         hintRequested = FALSE;
7324         lastHint[0] = NULLCHAR;
7325         bookRequested = FALSE;
7326         /* Program may be pondering now */
7327         cps->maybeThinking = TRUE;
7328         if (cps->sendTime == 2) cps->sendTime = 1;
7329         if (cps->offeredDraw) cps->offeredDraw--;
7330
7331         /* currentMoveString is set as a side-effect of ParseOneMove */
7332         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7333         strcat(machineMove, "\n");
7334         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7335
7336         /* [AS] Save move info*/
7337         pvInfoList[ forwardMostMove ].score = programStats.score;
7338         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7339         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7340
7341         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7342
7343         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7344         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7345             int count = 0;
7346
7347             while( count < adjudicateLossPlies ) {
7348                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7349
7350                 if( count & 1 ) {
7351                     score = -score; /* Flip score for winning side */
7352                 }
7353
7354                 if( score > adjudicateLossThreshold ) {
7355                     break;
7356                 }
7357
7358                 count++;
7359             }
7360
7361             if( count >= adjudicateLossPlies ) {
7362                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7363
7364                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7365                     "Xboard adjudication",
7366                     GE_XBOARD );
7367
7368                 return;
7369             }
7370         }
7371
7372         if(Adjudicate(cps)) {
7373             DrawPosition(FALSE, boards[currentMove = forwardMostMove-1]);
7374             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7375             return; // [HGM] adjudicate: for all automatic game ends
7376         }
7377
7378 #if ZIPPY
7379         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7380             first.initDone) {
7381           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7382                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7383                 SendToICS("draw ");
7384                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7385           }
7386           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7387           ics_user_moved = 1;
7388           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7389                 char buf[3*MSG_SIZ];
7390
7391                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7392                         programStats.score / 100.,
7393                         programStats.depth,
7394                         programStats.time / 100.,
7395                         (unsigned int)programStats.nodes,
7396                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7397                         programStats.movelist);
7398                 SendToICS(buf);
7399 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7400           }
7401         }
7402 #endif
7403
7404         /* [AS] Clear stats for next move */
7405         ClearProgramStats();
7406         thinkOutput[0] = NULLCHAR;
7407         hiddenThinkOutputState = 0;
7408
7409         bookHit = NULL;
7410         if (gameMode == TwoMachinesPlay) {
7411             /* [HGM] relaying draw offers moved to after reception of move */
7412             /* and interpreting offer as claim if it brings draw condition */
7413             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7414                 SendToProgram("draw\n", cps->other);
7415             }
7416             if (cps->other->sendTime) {
7417                 SendTimeRemaining(cps->other,
7418                                   cps->other->twoMachinesColor[0] == 'w');
7419             }
7420             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7421             if (firstMove && !bookHit) {
7422                 firstMove = FALSE;
7423                 if (cps->other->useColors) {
7424                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7425                 }
7426                 SendToProgram("go\n", cps->other);
7427             }
7428             cps->other->maybeThinking = TRUE;
7429         }
7430
7431         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7432
7433         if (!pausing && appData.ringBellAfterMoves) {
7434             RingBell();
7435         }
7436
7437         /*
7438          * Reenable menu items that were disabled while
7439          * machine was thinking
7440          */
7441         if (gameMode != TwoMachinesPlay)
7442             SetUserThinkingEnables();
7443
7444         // [HGM] book: after book hit opponent has received move and is now in force mode
7445         // force the book reply into it, and then fake that it outputted this move by jumping
7446         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7447         if(bookHit) {
7448                 static char bookMove[MSG_SIZ]; // a bit generous?
7449
7450                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7451                 strcat(bookMove, bookHit);
7452                 message = bookMove;
7453                 cps = cps->other;
7454                 programStats.nodes = programStats.depth = programStats.time =
7455                 programStats.score = programStats.got_only_move = 0;
7456                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7457
7458                 if(cps->lastPing != cps->lastPong) {
7459                     savedMessage = message; // args for deferred call
7460                     savedState = cps;
7461                     ScheduleDelayedEvent(DeferredBookMove, 10);
7462                     return;
7463                 }
7464                 goto FakeBookMove;
7465         }
7466
7467         return;
7468     }
7469
7470     /* Set special modes for chess engines.  Later something general
7471      *  could be added here; for now there is just one kludge feature,
7472      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7473      *  when "xboard" is given as an interactive command.
7474      */
7475     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7476         cps->useSigint = FALSE;
7477         cps->useSigterm = FALSE;
7478     }
7479     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7480       ParseFeatures(message+8, cps);
7481       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7482     }
7483
7484     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7485       int dummy, s=6; char buf[MSG_SIZ];
7486       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7487       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7488       ParseFEN(boards[0], &dummy, message+s);
7489       DrawPosition(TRUE, boards[0]);
7490       startedFromSetupPosition = TRUE;
7491       return;
7492     }
7493     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7494      * want this, I was asked to put it in, and obliged.
7495      */
7496     if (!strncmp(message, "setboard ", 9)) {
7497         Board initial_position;
7498
7499         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7500
7501         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7502             DisplayError(_("Bad FEN received from engine"), 0);
7503             return ;
7504         } else {
7505            Reset(TRUE, FALSE);
7506            CopyBoard(boards[0], initial_position);
7507            initialRulePlies = FENrulePlies;
7508            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7509            else gameMode = MachinePlaysBlack;
7510            DrawPosition(FALSE, boards[currentMove]);
7511         }
7512         return;
7513     }
7514
7515     /*
7516      * Look for communication commands
7517      */
7518     if (!strncmp(message, "telluser ", 9)) {
7519         if(message[9] == '\\' && message[10] == '\\')
7520             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7521         DisplayNote(message + 9);
7522         return;
7523     }
7524     if (!strncmp(message, "tellusererror ", 14)) {
7525         cps->userError = 1;
7526         if(message[14] == '\\' && message[15] == '\\')
7527             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7528         DisplayError(message + 14, 0);
7529         return;
7530     }
7531     if (!strncmp(message, "tellopponent ", 13)) {
7532       if (appData.icsActive) {
7533         if (loggedOn) {
7534           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7535           SendToICS(buf1);
7536         }
7537       } else {
7538         DisplayNote(message + 13);
7539       }
7540       return;
7541     }
7542     if (!strncmp(message, "tellothers ", 11)) {
7543       if (appData.icsActive) {
7544         if (loggedOn) {
7545           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7546           SendToICS(buf1);
7547         }
7548       }
7549       return;
7550     }
7551     if (!strncmp(message, "tellall ", 8)) {
7552       if (appData.icsActive) {
7553         if (loggedOn) {
7554           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7555           SendToICS(buf1);
7556         }
7557       } else {
7558         DisplayNote(message + 8);
7559       }
7560       return;
7561     }
7562     if (strncmp(message, "warning", 7) == 0) {
7563         /* Undocumented feature, use tellusererror in new code */
7564         DisplayError(message, 0);
7565         return;
7566     }
7567     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7568         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7569         strcat(realname, " query");
7570         AskQuestion(realname, buf2, buf1, cps->pr);
7571         return;
7572     }
7573     /* Commands from the engine directly to ICS.  We don't allow these to be
7574      *  sent until we are logged on. Crafty kibitzes have been known to
7575      *  interfere with the login process.
7576      */
7577     if (loggedOn) {
7578         if (!strncmp(message, "tellics ", 8)) {
7579             SendToICS(message + 8);
7580             SendToICS("\n");
7581             return;
7582         }
7583         if (!strncmp(message, "tellicsnoalias ", 15)) {
7584             SendToICS(ics_prefix);
7585             SendToICS(message + 15);
7586             SendToICS("\n");
7587             return;
7588         }
7589         /* The following are for backward compatibility only */
7590         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7591             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7592             SendToICS(ics_prefix);
7593             SendToICS(message);
7594             SendToICS("\n");
7595             return;
7596         }
7597     }
7598     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7599         return;
7600     }
7601     /*
7602      * If the move is illegal, cancel it and redraw the board.
7603      * Also deal with other error cases.  Matching is rather loose
7604      * here to accommodate engines written before the spec.
7605      */
7606     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7607         strncmp(message, "Error", 5) == 0) {
7608         if (StrStr(message, "name") ||
7609             StrStr(message, "rating") || StrStr(message, "?") ||
7610             StrStr(message, "result") || StrStr(message, "board") ||
7611             StrStr(message, "bk") || StrStr(message, "computer") ||
7612             StrStr(message, "variant") || StrStr(message, "hint") ||
7613             StrStr(message, "random") || StrStr(message, "depth") ||
7614             StrStr(message, "accepted")) {
7615             return;
7616         }
7617         if (StrStr(message, "protover")) {
7618           /* Program is responding to input, so it's apparently done
7619              initializing, and this error message indicates it is
7620              protocol version 1.  So we don't need to wait any longer
7621              for it to initialize and send feature commands. */
7622           FeatureDone(cps, 1);
7623           cps->protocolVersion = 1;
7624           return;
7625         }
7626         cps->maybeThinking = FALSE;
7627
7628         if (StrStr(message, "draw")) {
7629             /* Program doesn't have "draw" command */
7630             cps->sendDrawOffers = 0;
7631             return;
7632         }
7633         if (cps->sendTime != 1 &&
7634             (StrStr(message, "time") || StrStr(message, "otim"))) {
7635           /* Program apparently doesn't have "time" or "otim" command */
7636           cps->sendTime = 0;
7637           return;
7638         }
7639         if (StrStr(message, "analyze")) {
7640             cps->analysisSupport = FALSE;
7641             cps->analyzing = FALSE;
7642             Reset(FALSE, TRUE);
7643             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7644             DisplayError(buf2, 0);
7645             return;
7646         }
7647         if (StrStr(message, "(no matching move)st")) {
7648           /* Special kludge for GNU Chess 4 only */
7649           cps->stKludge = TRUE;
7650           SendTimeControl(cps, movesPerSession, timeControl,
7651                           timeIncrement, appData.searchDepth,
7652                           searchTime);
7653           return;
7654         }
7655         if (StrStr(message, "(no matching move)sd")) {
7656           /* Special kludge for GNU Chess 4 only */
7657           cps->sdKludge = TRUE;
7658           SendTimeControl(cps, movesPerSession, timeControl,
7659                           timeIncrement, appData.searchDepth,
7660                           searchTime);
7661           return;
7662         }
7663         if (!StrStr(message, "llegal")) {
7664             return;
7665         }
7666         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7667             gameMode == IcsIdle) return;
7668         if (forwardMostMove <= backwardMostMove) return;
7669         if (pausing) PauseEvent();
7670       if(appData.forceIllegal) {
7671             // [HGM] illegal: machine refused move; force position after move into it
7672           SendToProgram("force\n", cps);
7673           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7674                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7675                 // when black is to move, while there might be nothing on a2 or black
7676                 // might already have the move. So send the board as if white has the move.
7677                 // But first we must change the stm of the engine, as it refused the last move
7678                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7679                 if(WhiteOnMove(forwardMostMove)) {
7680                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7681                     SendBoard(cps, forwardMostMove); // kludgeless board
7682                 } else {
7683                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7684                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7685                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7686                 }
7687           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7688             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7689                  gameMode == TwoMachinesPlay)
7690               SendToProgram("go\n", cps);
7691             return;
7692       } else
7693         if (gameMode == PlayFromGameFile) {
7694             /* Stop reading this game file */
7695             gameMode = EditGame;
7696             ModeHighlight();
7697         }
7698         /* [HGM] illegal-move claim should forfeit game when Xboard */
7699         /* only passes fully legal moves                            */
7700         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7701             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7702                                 "False illegal-move claim", GE_XBOARD );
7703             return; // do not take back move we tested as valid
7704         }
7705         currentMove = forwardMostMove-1;
7706         DisplayMove(currentMove-1); /* before DisplayMoveError */
7707         SwitchClocks(forwardMostMove-1); // [HGM] race
7708         DisplayBothClocks();
7709         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7710                 parseList[currentMove], cps->which);
7711         DisplayMoveError(buf1);
7712         DrawPosition(FALSE, boards[currentMove]);
7713         return;
7714     }
7715     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7716         /* Program has a broken "time" command that
7717            outputs a string not ending in newline.
7718            Don't use it. */
7719         cps->sendTime = 0;
7720     }
7721
7722     /*
7723      * If chess program startup fails, exit with an error message.
7724      * Attempts to recover here are futile.
7725      */
7726     if ((StrStr(message, "unknown host") != NULL)
7727         || (StrStr(message, "No remote directory") != NULL)
7728         || (StrStr(message, "not found") != NULL)
7729         || (StrStr(message, "No such file") != NULL)
7730         || (StrStr(message, "can't alloc") != NULL)
7731         || (StrStr(message, "Permission denied") != NULL)) {
7732
7733         cps->maybeThinking = FALSE;
7734         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7735                 cps->which, cps->program, cps->host, message);
7736         RemoveInputSource(cps->isr);
7737         DisplayFatalError(buf1, 0, 1);
7738         return;
7739     }
7740
7741     /*
7742      * Look for hint output
7743      */
7744     if (sscanf(message, "Hint: %s", buf1) == 1) {
7745         if (cps == &first && hintRequested) {
7746             hintRequested = FALSE;
7747             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7748                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7749                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7750                                     PosFlags(forwardMostMove),
7751                                     fromY, fromX, toY, toX, promoChar, buf1);
7752                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7753                 DisplayInformation(buf2);
7754             } else {
7755                 /* Hint move could not be parsed!? */
7756               snprintf(buf2, sizeof(buf2),
7757                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7758                         buf1, cps->which);
7759                 DisplayError(buf2, 0);
7760             }
7761         } else {
7762           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7763         }
7764         return;
7765     }
7766
7767     /*
7768      * Ignore other messages if game is not in progress
7769      */
7770     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7771         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7772
7773     /*
7774      * look for win, lose, draw, or draw offer
7775      */
7776     if (strncmp(message, "1-0", 3) == 0) {
7777         char *p, *q, *r = "";
7778         p = strchr(message, '{');
7779         if (p) {
7780             q = strchr(p, '}');
7781             if (q) {
7782                 *q = NULLCHAR;
7783                 r = p + 1;
7784             }
7785         }
7786         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7787         return;
7788     } else if (strncmp(message, "0-1", 3) == 0) {
7789         char *p, *q, *r = "";
7790         p = strchr(message, '{');
7791         if (p) {
7792             q = strchr(p, '}');
7793             if (q) {
7794                 *q = NULLCHAR;
7795                 r = p + 1;
7796             }
7797         }
7798         /* Kludge for Arasan 4.1 bug */
7799         if (strcmp(r, "Black resigns") == 0) {
7800             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7801             return;
7802         }
7803         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7804         return;
7805     } else if (strncmp(message, "1/2", 3) == 0) {
7806         char *p, *q, *r = "";
7807         p = strchr(message, '{');
7808         if (p) {
7809             q = strchr(p, '}');
7810             if (q) {
7811                 *q = NULLCHAR;
7812                 r = p + 1;
7813             }
7814         }
7815
7816         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7817         return;
7818
7819     } else if (strncmp(message, "White resign", 12) == 0) {
7820         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7821         return;
7822     } else if (strncmp(message, "Black resign", 12) == 0) {
7823         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7824         return;
7825     } else if (strncmp(message, "White matches", 13) == 0 ||
7826                strncmp(message, "Black matches", 13) == 0   ) {
7827         /* [HGM] ignore GNUShogi noises */
7828         return;
7829     } else if (strncmp(message, "White", 5) == 0 &&
7830                message[5] != '(' &&
7831                StrStr(message, "Black") == NULL) {
7832         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7833         return;
7834     } else if (strncmp(message, "Black", 5) == 0 &&
7835                message[5] != '(') {
7836         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7837         return;
7838     } else if (strcmp(message, "resign") == 0 ||
7839                strcmp(message, "computer resigns") == 0) {
7840         switch (gameMode) {
7841           case MachinePlaysBlack:
7842           case IcsPlayingBlack:
7843             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7844             break;
7845           case MachinePlaysWhite:
7846           case IcsPlayingWhite:
7847             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7848             break;
7849           case TwoMachinesPlay:
7850             if (cps->twoMachinesColor[0] == 'w')
7851               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7852             else
7853               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7854             break;
7855           default:
7856             /* can't happen */
7857             break;
7858         }
7859         return;
7860     } else if (strncmp(message, "opponent mates", 14) == 0) {
7861         switch (gameMode) {
7862           case MachinePlaysBlack:
7863           case IcsPlayingBlack:
7864             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7865             break;
7866           case MachinePlaysWhite:
7867           case IcsPlayingWhite:
7868             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7869             break;
7870           case TwoMachinesPlay:
7871             if (cps->twoMachinesColor[0] == 'w')
7872               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7873             else
7874               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7875             break;
7876           default:
7877             /* can't happen */
7878             break;
7879         }
7880         return;
7881     } else if (strncmp(message, "computer mates", 14) == 0) {
7882         switch (gameMode) {
7883           case MachinePlaysBlack:
7884           case IcsPlayingBlack:
7885             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7886             break;
7887           case MachinePlaysWhite:
7888           case IcsPlayingWhite:
7889             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7890             break;
7891           case TwoMachinesPlay:
7892             if (cps->twoMachinesColor[0] == 'w')
7893               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7894             else
7895               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7896             break;
7897           default:
7898             /* can't happen */
7899             break;
7900         }
7901         return;
7902     } else if (strncmp(message, "checkmate", 9) == 0) {
7903         if (WhiteOnMove(forwardMostMove)) {
7904             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7905         } else {
7906             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7907         }
7908         return;
7909     } else if (strstr(message, "Draw") != NULL ||
7910                strstr(message, "game is a draw") != NULL) {
7911         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7912         return;
7913     } else if (strstr(message, "offer") != NULL &&
7914                strstr(message, "draw") != NULL) {
7915 #if ZIPPY
7916         if (appData.zippyPlay && first.initDone) {
7917             /* Relay offer to ICS */
7918             SendToICS(ics_prefix);
7919             SendToICS("draw\n");
7920         }
7921 #endif
7922         cps->offeredDraw = 2; /* valid until this engine moves twice */
7923         if (gameMode == TwoMachinesPlay) {
7924             if (cps->other->offeredDraw) {
7925                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7926             /* [HGM] in two-machine mode we delay relaying draw offer      */
7927             /* until after we also have move, to see if it is really claim */
7928             }
7929         } else if (gameMode == MachinePlaysWhite ||
7930                    gameMode == MachinePlaysBlack) {
7931           if (userOfferedDraw) {
7932             DisplayInformation(_("Machine accepts your draw offer"));
7933             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7934           } else {
7935             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7936           }
7937         }
7938     }
7939
7940
7941     /*
7942      * Look for thinking output
7943      */
7944     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7945           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7946                                 ) {
7947         int plylev, mvleft, mvtot, curscore, time;
7948         char mvname[MOVE_LEN];
7949         u64 nodes; // [DM]
7950         char plyext;
7951         int ignore = FALSE;
7952         int prefixHint = FALSE;
7953         mvname[0] = NULLCHAR;
7954
7955         switch (gameMode) {
7956           case MachinePlaysBlack:
7957           case IcsPlayingBlack:
7958             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7959             break;
7960           case MachinePlaysWhite:
7961           case IcsPlayingWhite:
7962             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7963             break;
7964           case AnalyzeMode:
7965           case AnalyzeFile:
7966             break;
7967           case IcsObserving: /* [DM] icsEngineAnalyze */
7968             if (!appData.icsEngineAnalyze) ignore = TRUE;
7969             break;
7970           case TwoMachinesPlay:
7971             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7972                 ignore = TRUE;
7973             }
7974             break;
7975           default:
7976             ignore = TRUE;
7977             break;
7978         }
7979
7980         if (!ignore) {
7981             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7982             buf1[0] = NULLCHAR;
7983             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7984                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7985
7986                 if (plyext != ' ' && plyext != '\t') {
7987                     time *= 100;
7988                 }
7989
7990                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7991                 if( cps->scoreIsAbsolute &&
7992                     ( gameMode == MachinePlaysBlack ||
7993                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7994                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7995                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7996                      !WhiteOnMove(currentMove)
7997                     ) )
7998                 {
7999                     curscore = -curscore;
8000                 }
8001
8002
8003                 tempStats.depth = plylev;
8004                 tempStats.nodes = nodes;
8005                 tempStats.time = time;
8006                 tempStats.score = curscore;
8007                 tempStats.got_only_move = 0;
8008
8009                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8010                         int ticklen;
8011
8012                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8013                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8014                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8015                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8016                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8017                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8018                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8019                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8020                 }
8021
8022                 /* Buffer overflow protection */
8023                 if (buf1[0] != NULLCHAR) {
8024                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8025                         && appData.debugMode) {
8026                         fprintf(debugFP,
8027                                 "PV is too long; using the first %u bytes.\n",
8028                                 (unsigned) sizeof(tempStats.movelist) - 1);
8029                     }
8030
8031                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8032                 } else {
8033                     sprintf(tempStats.movelist, " no PV\n");
8034                 }
8035
8036                 if (tempStats.seen_stat) {
8037                     tempStats.ok_to_send = 1;
8038                 }
8039
8040                 if (strchr(tempStats.movelist, '(') != NULL) {
8041                     tempStats.line_is_book = 1;
8042                     tempStats.nr_moves = 0;
8043                     tempStats.moves_left = 0;
8044                 } else {
8045                     tempStats.line_is_book = 0;
8046                 }
8047
8048                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8049                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8050
8051                 SendProgramStatsToFrontend( cps, &tempStats );
8052
8053                 /*
8054                     [AS] Protect the thinkOutput buffer from overflow... this
8055                     is only useful if buf1 hasn't overflowed first!
8056                 */
8057                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8058                          plylev,
8059                          (gameMode == TwoMachinesPlay ?
8060                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8061                          ((double) curscore) / 100.0,
8062                          prefixHint ? lastHint : "",
8063                          prefixHint ? " " : "" );
8064
8065                 if( buf1[0] != NULLCHAR ) {
8066                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8067
8068                     if( strlen(buf1) > max_len ) {
8069                         if( appData.debugMode) {
8070                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8071                         }
8072                         buf1[max_len+1] = '\0';
8073                     }
8074
8075                     strcat( thinkOutput, buf1 );
8076                 }
8077
8078                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8079                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8080                     DisplayMove(currentMove - 1);
8081                 }
8082                 return;
8083
8084             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8085                 /* crafty (9.25+) says "(only move) <move>"
8086                  * if there is only 1 legal move
8087                  */
8088                 sscanf(p, "(only move) %s", buf1);
8089                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8090                 sprintf(programStats.movelist, "%s (only move)", buf1);
8091                 programStats.depth = 1;
8092                 programStats.nr_moves = 1;
8093                 programStats.moves_left = 1;
8094                 programStats.nodes = 1;
8095                 programStats.time = 1;
8096                 programStats.got_only_move = 1;
8097
8098                 /* Not really, but we also use this member to
8099                    mean "line isn't going to change" (Crafty
8100                    isn't searching, so stats won't change) */
8101                 programStats.line_is_book = 1;
8102
8103                 SendProgramStatsToFrontend( cps, &programStats );
8104
8105                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8106                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8107                     DisplayMove(currentMove - 1);
8108                 }
8109                 return;
8110             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8111                               &time, &nodes, &plylev, &mvleft,
8112                               &mvtot, mvname) >= 5) {
8113                 /* The stat01: line is from Crafty (9.29+) in response
8114                    to the "." command */
8115                 programStats.seen_stat = 1;
8116                 cps->maybeThinking = TRUE;
8117
8118                 if (programStats.got_only_move || !appData.periodicUpdates)
8119                   return;
8120
8121                 programStats.depth = plylev;
8122                 programStats.time = time;
8123                 programStats.nodes = nodes;
8124                 programStats.moves_left = mvleft;
8125                 programStats.nr_moves = mvtot;
8126                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8127                 programStats.ok_to_send = 1;
8128                 programStats.movelist[0] = '\0';
8129
8130                 SendProgramStatsToFrontend( cps, &programStats );
8131
8132                 return;
8133
8134             } else if (strncmp(message,"++",2) == 0) {
8135                 /* Crafty 9.29+ outputs this */
8136                 programStats.got_fail = 2;
8137                 return;
8138
8139             } else if (strncmp(message,"--",2) == 0) {
8140                 /* Crafty 9.29+ outputs this */
8141                 programStats.got_fail = 1;
8142                 return;
8143
8144             } else if (thinkOutput[0] != NULLCHAR &&
8145                        strncmp(message, "    ", 4) == 0) {
8146                 unsigned message_len;
8147
8148                 p = message;
8149                 while (*p && *p == ' ') p++;
8150
8151                 message_len = strlen( p );
8152
8153                 /* [AS] Avoid buffer overflow */
8154                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8155                     strcat(thinkOutput, " ");
8156                     strcat(thinkOutput, p);
8157                 }
8158
8159                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8160                     strcat(programStats.movelist, " ");
8161                     strcat(programStats.movelist, p);
8162                 }
8163
8164                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8165                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8166                     DisplayMove(currentMove - 1);
8167                 }
8168                 return;
8169             }
8170         }
8171         else {
8172             buf1[0] = NULLCHAR;
8173
8174             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8175                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8176             {
8177                 ChessProgramStats cpstats;
8178
8179                 if (plyext != ' ' && plyext != '\t') {
8180                     time *= 100;
8181                 }
8182
8183                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8184                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8185                     curscore = -curscore;
8186                 }
8187
8188                 cpstats.depth = plylev;
8189                 cpstats.nodes = nodes;
8190                 cpstats.time = time;
8191                 cpstats.score = curscore;
8192                 cpstats.got_only_move = 0;
8193                 cpstats.movelist[0] = '\0';
8194
8195                 if (buf1[0] != NULLCHAR) {
8196                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8197                 }
8198
8199                 cpstats.ok_to_send = 0;
8200                 cpstats.line_is_book = 0;
8201                 cpstats.nr_moves = 0;
8202                 cpstats.moves_left = 0;
8203
8204                 SendProgramStatsToFrontend( cps, &cpstats );
8205             }
8206         }
8207     }
8208 }
8209
8210
8211 /* Parse a game score from the character string "game", and
8212    record it as the history of the current game.  The game
8213    score is NOT assumed to start from the standard position.
8214    The display is not updated in any way.
8215    */
8216 void
8217 ParseGameHistory(game)
8218      char *game;
8219 {
8220     ChessMove moveType;
8221     int fromX, fromY, toX, toY, boardIndex;
8222     char promoChar;
8223     char *p, *q;
8224     char buf[MSG_SIZ];
8225
8226     if (appData.debugMode)
8227       fprintf(debugFP, "Parsing game history: %s\n", game);
8228
8229     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8230     gameInfo.site = StrSave(appData.icsHost);
8231     gameInfo.date = PGNDate();
8232     gameInfo.round = StrSave("-");
8233
8234     /* Parse out names of players */
8235     while (*game == ' ') game++;
8236     p = buf;
8237     while (*game != ' ') *p++ = *game++;
8238     *p = NULLCHAR;
8239     gameInfo.white = StrSave(buf);
8240     while (*game == ' ') game++;
8241     p = buf;
8242     while (*game != ' ' && *game != '\n') *p++ = *game++;
8243     *p = NULLCHAR;
8244     gameInfo.black = StrSave(buf);
8245
8246     /* Parse moves */
8247     boardIndex = blackPlaysFirst ? 1 : 0;
8248     yynewstr(game);
8249     for (;;) {
8250         yyboardindex = boardIndex;
8251         moveType = (ChessMove) Myylex();
8252         switch (moveType) {
8253           case IllegalMove:             /* maybe suicide chess, etc. */
8254   if (appData.debugMode) {
8255     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8256     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8257     setbuf(debugFP, NULL);
8258   }
8259           case WhitePromotion:
8260           case BlackPromotion:
8261           case WhiteNonPromotion:
8262           case BlackNonPromotion:
8263           case NormalMove:
8264           case WhiteCapturesEnPassant:
8265           case BlackCapturesEnPassant:
8266           case WhiteKingSideCastle:
8267           case WhiteQueenSideCastle:
8268           case BlackKingSideCastle:
8269           case BlackQueenSideCastle:
8270           case WhiteKingSideCastleWild:
8271           case WhiteQueenSideCastleWild:
8272           case BlackKingSideCastleWild:
8273           case BlackQueenSideCastleWild:
8274           /* PUSH Fabien */
8275           case WhiteHSideCastleFR:
8276           case WhiteASideCastleFR:
8277           case BlackHSideCastleFR:
8278           case BlackASideCastleFR:
8279           /* POP Fabien */
8280             fromX = currentMoveString[0] - AAA;
8281             fromY = currentMoveString[1] - ONE;
8282             toX = currentMoveString[2] - AAA;
8283             toY = currentMoveString[3] - ONE;
8284             promoChar = currentMoveString[4];
8285             break;
8286           case WhiteDrop:
8287           case BlackDrop:
8288             fromX = moveType == WhiteDrop ?
8289               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8290             (int) CharToPiece(ToLower(currentMoveString[0]));
8291             fromY = DROP_RANK;
8292             toX = currentMoveString[2] - AAA;
8293             toY = currentMoveString[3] - ONE;
8294             promoChar = NULLCHAR;
8295             break;
8296           case AmbiguousMove:
8297             /* bug? */
8298             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8299   if (appData.debugMode) {
8300     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8301     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8302     setbuf(debugFP, NULL);
8303   }
8304             DisplayError(buf, 0);
8305             return;
8306           case ImpossibleMove:
8307             /* bug? */
8308             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8309   if (appData.debugMode) {
8310     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8311     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8312     setbuf(debugFP, NULL);
8313   }
8314             DisplayError(buf, 0);
8315             return;
8316           case EndOfFile:
8317             if (boardIndex < backwardMostMove) {
8318                 /* Oops, gap.  How did that happen? */
8319                 DisplayError(_("Gap in move list"), 0);
8320                 return;
8321             }
8322             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8323             if (boardIndex > forwardMostMove) {
8324                 forwardMostMove = boardIndex;
8325             }
8326             return;
8327           case ElapsedTime:
8328             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8329                 strcat(parseList[boardIndex-1], " ");
8330                 strcat(parseList[boardIndex-1], yy_text);
8331             }
8332             continue;
8333           case Comment:
8334           case PGNTag:
8335           case NAG:
8336           default:
8337             /* ignore */
8338             continue;
8339           case WhiteWins:
8340           case BlackWins:
8341           case GameIsDrawn:
8342           case GameUnfinished:
8343             if (gameMode == IcsExamining) {
8344                 if (boardIndex < backwardMostMove) {
8345                     /* Oops, gap.  How did that happen? */
8346                     return;
8347                 }
8348                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8349                 return;
8350             }
8351             gameInfo.result = moveType;
8352             p = strchr(yy_text, '{');
8353             if (p == NULL) p = strchr(yy_text, '(');
8354             if (p == NULL) {
8355                 p = yy_text;
8356                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8357             } else {
8358                 q = strchr(p, *p == '{' ? '}' : ')');
8359                 if (q != NULL) *q = NULLCHAR;
8360                 p++;
8361             }
8362             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8363             gameInfo.resultDetails = StrSave(p);
8364             continue;
8365         }
8366         if (boardIndex >= forwardMostMove &&
8367             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8368             backwardMostMove = blackPlaysFirst ? 1 : 0;
8369             return;
8370         }
8371         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8372                                  fromY, fromX, toY, toX, promoChar,
8373                                  parseList[boardIndex]);
8374         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8375         /* currentMoveString is set as a side-effect of yylex */
8376         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8377         strcat(moveList[boardIndex], "\n");
8378         boardIndex++;
8379         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8380         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8381           case MT_NONE:
8382           case MT_STALEMATE:
8383           default:
8384             break;
8385           case MT_CHECK:
8386             if(gameInfo.variant != VariantShogi)
8387                 strcat(parseList[boardIndex - 1], "+");
8388             break;
8389           case MT_CHECKMATE:
8390           case MT_STAINMATE:
8391             strcat(parseList[boardIndex - 1], "#");
8392             break;
8393         }
8394     }
8395 }
8396
8397
8398 /* Apply a move to the given board  */
8399 void
8400 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8401      int fromX, fromY, toX, toY;
8402      int promoChar;
8403      Board board;
8404 {
8405   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8406   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8407
8408     /* [HGM] compute & store e.p. status and castling rights for new position */
8409     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8410
8411       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8412       oldEP = (signed char)board[EP_STATUS];
8413       board[EP_STATUS] = EP_NONE;
8414
8415       if( board[toY][toX] != EmptySquare )
8416            board[EP_STATUS] = EP_CAPTURE;
8417
8418   if (fromY == DROP_RANK) {
8419         /* must be first */
8420         piece = board[toY][toX] = (ChessSquare) fromX;
8421   } else {
8422       int i;
8423
8424       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8425            if( gameInfo.variant == VariantFairy ) board[EP_STATUS] = EP_PAWN_MOVE; // Lance in fairy is Pawn-like
8426       } else
8427       if( board[fromY][fromX] == WhitePawn ) {
8428            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8429                board[EP_STATUS] = EP_PAWN_MOVE;
8430            if( toY-fromY==2) {
8431                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8432                         gameInfo.variant != VariantBerolina || toX < fromX)
8433                       board[EP_STATUS] = toX | berolina;
8434                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8435                         gameInfo.variant != VariantBerolina || toX > fromX)
8436                       board[EP_STATUS] = toX;
8437            }
8438       } else
8439       if( board[fromY][fromX] == BlackPawn ) {
8440            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8441                board[EP_STATUS] = EP_PAWN_MOVE;
8442            if( toY-fromY== -2) {
8443                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8444                         gameInfo.variant != VariantBerolina || toX < fromX)
8445                       board[EP_STATUS] = toX | berolina;
8446                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8447                         gameInfo.variant != VariantBerolina || toX > fromX)
8448                       board[EP_STATUS] = toX;
8449            }
8450        }
8451
8452        for(i=0; i<nrCastlingRights; i++) {
8453            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8454               board[CASTLING][i] == toX   && castlingRank[i] == toY
8455              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8456        }
8457
8458      if (fromX == toX && fromY == toY) return;
8459
8460      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8461      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8462      if(gameInfo.variant == VariantKnightmate)
8463          king += (int) WhiteUnicorn - (int) WhiteKing;
8464
8465     /* Code added by Tord: */
8466     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8467     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8468         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8469       board[fromY][fromX] = EmptySquare;
8470       board[toY][toX] = EmptySquare;
8471       if((toX > fromX) != (piece == WhiteRook)) {
8472         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8473       } else {
8474         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8475       }
8476     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8477                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8478       board[fromY][fromX] = EmptySquare;
8479       board[toY][toX] = EmptySquare;
8480       if((toX > fromX) != (piece == BlackRook)) {
8481         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8482       } else {
8483         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8484       }
8485     /* End of code added by Tord */
8486
8487     } else if (board[fromY][fromX] == king
8488         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8489         && toY == fromY && toX > fromX+1) {
8490         board[fromY][fromX] = EmptySquare;
8491         board[toY][toX] = king;
8492         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8493         board[fromY][BOARD_RGHT-1] = EmptySquare;
8494     } else if (board[fromY][fromX] == king
8495         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8496                && toY == fromY && toX < fromX-1) {
8497         board[fromY][fromX] = EmptySquare;
8498         board[toY][toX] = king;
8499         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8500         board[fromY][BOARD_LEFT] = EmptySquare;
8501     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8502                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8503                && toY >= BOARD_HEIGHT-promoRank
8504                ) {
8505         /* white pawn promotion */
8506         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8507         if (board[toY][toX] == EmptySquare) {
8508             board[toY][toX] = WhiteQueen;
8509         }
8510         if(gameInfo.variant==VariantBughouse ||
8511            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8512             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8513         board[fromY][fromX] = EmptySquare;
8514     } else if ((fromY == BOARD_HEIGHT-4)
8515                && (toX != fromX)
8516                && gameInfo.variant != VariantXiangqi
8517                && gameInfo.variant != VariantBerolina
8518                && (board[fromY][fromX] == WhitePawn)
8519                && (board[toY][toX] == EmptySquare)) {
8520         board[fromY][fromX] = EmptySquare;
8521         board[toY][toX] = WhitePawn;
8522         captured = board[toY - 1][toX];
8523         board[toY - 1][toX] = EmptySquare;
8524     } else if ((fromY == BOARD_HEIGHT-4)
8525                && (toX == fromX)
8526                && gameInfo.variant == VariantBerolina
8527                && (board[fromY][fromX] == WhitePawn)
8528                && (board[toY][toX] == EmptySquare)) {
8529         board[fromY][fromX] = EmptySquare;
8530         board[toY][toX] = WhitePawn;
8531         if(oldEP & EP_BEROLIN_A) {
8532                 captured = board[fromY][fromX-1];
8533                 board[fromY][fromX-1] = EmptySquare;
8534         }else{  captured = board[fromY][fromX+1];
8535                 board[fromY][fromX+1] = EmptySquare;
8536         }
8537     } else if (board[fromY][fromX] == king
8538         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8539                && toY == fromY && toX > fromX+1) {
8540         board[fromY][fromX] = EmptySquare;
8541         board[toY][toX] = king;
8542         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8543         board[fromY][BOARD_RGHT-1] = EmptySquare;
8544     } else if (board[fromY][fromX] == king
8545         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8546                && toY == fromY && toX < fromX-1) {
8547         board[fromY][fromX] = EmptySquare;
8548         board[toY][toX] = king;
8549         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8550         board[fromY][BOARD_LEFT] = EmptySquare;
8551     } else if (fromY == 7 && fromX == 3
8552                && board[fromY][fromX] == BlackKing
8553                && toY == 7 && toX == 5) {
8554         board[fromY][fromX] = EmptySquare;
8555         board[toY][toX] = BlackKing;
8556         board[fromY][7] = EmptySquare;
8557         board[toY][4] = BlackRook;
8558     } else if (fromY == 7 && fromX == 3
8559                && board[fromY][fromX] == BlackKing
8560                && toY == 7 && toX == 1) {
8561         board[fromY][fromX] = EmptySquare;
8562         board[toY][toX] = BlackKing;
8563         board[fromY][0] = EmptySquare;
8564         board[toY][2] = BlackRook;
8565     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8566                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8567                && toY < promoRank
8568                ) {
8569         /* black pawn promotion */
8570         board[toY][toX] = CharToPiece(ToLower(promoChar));
8571         if (board[toY][toX] == EmptySquare) {
8572             board[toY][toX] = BlackQueen;
8573         }
8574         if(gameInfo.variant==VariantBughouse ||
8575            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8576             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8577         board[fromY][fromX] = EmptySquare;
8578     } else if ((fromY == 3)
8579                && (toX != fromX)
8580                && gameInfo.variant != VariantXiangqi
8581                && gameInfo.variant != VariantBerolina
8582                && (board[fromY][fromX] == BlackPawn)
8583                && (board[toY][toX] == EmptySquare)) {
8584         board[fromY][fromX] = EmptySquare;
8585         board[toY][toX] = BlackPawn;
8586         captured = board[toY + 1][toX];
8587         board[toY + 1][toX] = EmptySquare;
8588     } else if ((fromY == 3)
8589                && (toX == fromX)
8590                && gameInfo.variant == VariantBerolina
8591                && (board[fromY][fromX] == BlackPawn)
8592                && (board[toY][toX] == EmptySquare)) {
8593         board[fromY][fromX] = EmptySquare;
8594         board[toY][toX] = BlackPawn;
8595         if(oldEP & EP_BEROLIN_A) {
8596                 captured = board[fromY][fromX-1];
8597                 board[fromY][fromX-1] = EmptySquare;
8598         }else{  captured = board[fromY][fromX+1];
8599                 board[fromY][fromX+1] = EmptySquare;
8600         }
8601     } else {
8602         board[toY][toX] = board[fromY][fromX];
8603         board[fromY][fromX] = EmptySquare;
8604     }
8605   }
8606
8607     if (gameInfo.holdingsWidth != 0) {
8608
8609       /* !!A lot more code needs to be written to support holdings  */
8610       /* [HGM] OK, so I have written it. Holdings are stored in the */
8611       /* penultimate board files, so they are automaticlly stored   */
8612       /* in the game history.                                       */
8613       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8614                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8615         /* Delete from holdings, by decreasing count */
8616         /* and erasing image if necessary            */
8617         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8618         if(p < (int) BlackPawn) { /* white drop */
8619              p -= (int)WhitePawn;
8620                  p = PieceToNumber((ChessSquare)p);
8621              if(p >= gameInfo.holdingsSize) p = 0;
8622              if(--board[p][BOARD_WIDTH-2] <= 0)
8623                   board[p][BOARD_WIDTH-1] = EmptySquare;
8624              if((int)board[p][BOARD_WIDTH-2] < 0)
8625                         board[p][BOARD_WIDTH-2] = 0;
8626         } else {                  /* black drop */
8627              p -= (int)BlackPawn;
8628                  p = PieceToNumber((ChessSquare)p);
8629              if(p >= gameInfo.holdingsSize) p = 0;
8630              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8631                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8632              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8633                         board[BOARD_HEIGHT-1-p][1] = 0;
8634         }
8635       }
8636       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8637           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8638         /* [HGM] holdings: Add to holdings, if holdings exist */
8639         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8640                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8641                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8642         }
8643         p = (int) captured;
8644         if (p >= (int) BlackPawn) {
8645           p -= (int)BlackPawn;
8646           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8647                   /* in Shogi restore piece to its original  first */
8648                   captured = (ChessSquare) (DEMOTED captured);
8649                   p = DEMOTED p;
8650           }
8651           p = PieceToNumber((ChessSquare)p);
8652           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8653           board[p][BOARD_WIDTH-2]++;
8654           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8655         } else {
8656           p -= (int)WhitePawn;
8657           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8658                   captured = (ChessSquare) (DEMOTED captured);
8659                   p = DEMOTED p;
8660           }
8661           p = PieceToNumber((ChessSquare)p);
8662           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8663           board[BOARD_HEIGHT-1-p][1]++;
8664           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8665         }
8666       }
8667     } else if (gameInfo.variant == VariantAtomic) {
8668       if (captured != EmptySquare) {
8669         int y, x;
8670         for (y = toY-1; y <= toY+1; y++) {
8671           for (x = toX-1; x <= toX+1; x++) {
8672             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8673                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8674               board[y][x] = EmptySquare;
8675             }
8676           }
8677         }
8678         board[toY][toX] = EmptySquare;
8679       }
8680     }
8681     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8682         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8683     } else
8684     if(promoChar == '+') {
8685         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8686         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8687     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8688         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8689     }
8690     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8691                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8692         // [HGM] superchess: take promotion piece out of holdings
8693         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8694         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8695             if(!--board[k][BOARD_WIDTH-2])
8696                 board[k][BOARD_WIDTH-1] = EmptySquare;
8697         } else {
8698             if(!--board[BOARD_HEIGHT-1-k][1])
8699                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8700         }
8701     }
8702
8703 }
8704
8705 /* Updates forwardMostMove */
8706 void
8707 MakeMove(fromX, fromY, toX, toY, promoChar)
8708      int fromX, fromY, toX, toY;
8709      int promoChar;
8710 {
8711 //    forwardMostMove++; // [HGM] bare: moved downstream
8712
8713     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8714         int timeLeft; static int lastLoadFlag=0; int king, piece;
8715         piece = boards[forwardMostMove][fromY][fromX];
8716         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8717         if(gameInfo.variant == VariantKnightmate)
8718             king += (int) WhiteUnicorn - (int) WhiteKing;
8719         if(forwardMostMove == 0) {
8720             if(blackPlaysFirst)
8721                 fprintf(serverMoves, "%s;", second.tidy);
8722             fprintf(serverMoves, "%s;", first.tidy);
8723             if(!blackPlaysFirst)
8724                 fprintf(serverMoves, "%s;", second.tidy);
8725         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8726         lastLoadFlag = loadFlag;
8727         // print base move
8728         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8729         // print castling suffix
8730         if( toY == fromY && piece == king ) {
8731             if(toX-fromX > 1)
8732                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8733             if(fromX-toX >1)
8734                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8735         }
8736         // e.p. suffix
8737         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8738              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8739              boards[forwardMostMove][toY][toX] == EmptySquare
8740              && fromX != toX && fromY != toY)
8741                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8742         // promotion suffix
8743         if(promoChar != NULLCHAR)
8744                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8745         if(!loadFlag) {
8746             fprintf(serverMoves, "/%d/%d",
8747                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8748             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8749             else                      timeLeft = blackTimeRemaining/1000;
8750             fprintf(serverMoves, "/%d", timeLeft);
8751         }
8752         fflush(serverMoves);
8753     }
8754
8755     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8756       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8757                         0, 1);
8758       return;
8759     }
8760     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8761     if (commentList[forwardMostMove+1] != NULL) {
8762         free(commentList[forwardMostMove+1]);
8763         commentList[forwardMostMove+1] = NULL;
8764     }
8765     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8766     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8767     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8768     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8769     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8770     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8771     gameInfo.result = GameUnfinished;
8772     if (gameInfo.resultDetails != NULL) {
8773         free(gameInfo.resultDetails);
8774         gameInfo.resultDetails = NULL;
8775     }
8776     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8777                               moveList[forwardMostMove - 1]);
8778     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8779                              PosFlags(forwardMostMove - 1),
8780                              fromY, fromX, toY, toX, promoChar,
8781                              parseList[forwardMostMove - 1]);
8782     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8783       case MT_NONE:
8784       case MT_STALEMATE:
8785       default:
8786         break;
8787       case MT_CHECK:
8788         if(gameInfo.variant != VariantShogi)
8789             strcat(parseList[forwardMostMove - 1], "+");
8790         break;
8791       case MT_CHECKMATE:
8792       case MT_STAINMATE:
8793         strcat(parseList[forwardMostMove - 1], "#");
8794         break;
8795     }
8796     if (appData.debugMode) {
8797         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8798     }
8799
8800 }
8801
8802 /* Updates currentMove if not pausing */
8803 void
8804 ShowMove(fromX, fromY, toX, toY)
8805 {
8806     int instant = (gameMode == PlayFromGameFile) ?
8807         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8808     if(appData.noGUI) return;
8809     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8810         if (!instant) {
8811             if (forwardMostMove == currentMove + 1) {
8812                 AnimateMove(boards[forwardMostMove - 1],
8813                             fromX, fromY, toX, toY);
8814             }
8815             if (appData.highlightLastMove) {
8816                 SetHighlights(fromX, fromY, toX, toY);
8817             }
8818         }
8819         currentMove = forwardMostMove;
8820     }
8821
8822     if (instant) return;
8823
8824     DisplayMove(currentMove - 1);
8825     DrawPosition(FALSE, boards[currentMove]);
8826     DisplayBothClocks();
8827     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8828 }
8829
8830 void SendEgtPath(ChessProgramState *cps)
8831 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8832         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8833
8834         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8835
8836         while(*p) {
8837             char c, *q = name+1, *r, *s;
8838
8839             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8840             while(*p && *p != ',') *q++ = *p++;
8841             *q++ = ':'; *q = 0;
8842             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8843                 strcmp(name, ",nalimov:") == 0 ) {
8844                 // take nalimov path from the menu-changeable option first, if it is defined
8845               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8846                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8847             } else
8848             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8849                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8850                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8851                 s = r = StrStr(s, ":") + 1; // beginning of path info
8852                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8853                 c = *r; *r = 0;             // temporarily null-terminate path info
8854                     *--q = 0;               // strip of trailig ':' from name
8855                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8856                 *r = c;
8857                 SendToProgram(buf,cps);     // send egtbpath command for this format
8858             }
8859             if(*p == ',') p++; // read away comma to position for next format name
8860         }
8861 }
8862
8863 void
8864 InitChessProgram(cps, setup)
8865      ChessProgramState *cps;
8866      int setup; /* [HGM] needed to setup FRC opening position */
8867 {
8868     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8869     if (appData.noChessProgram) return;
8870     hintRequested = FALSE;
8871     bookRequested = FALSE;
8872
8873     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8874     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8875     if(cps->memSize) { /* [HGM] memory */
8876       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8877         SendToProgram(buf, cps);
8878     }
8879     SendEgtPath(cps); /* [HGM] EGT */
8880     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8881       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8882         SendToProgram(buf, cps);
8883     }
8884
8885     SendToProgram(cps->initString, cps);
8886     if (gameInfo.variant != VariantNormal &&
8887         gameInfo.variant != VariantLoadable
8888         /* [HGM] also send variant if board size non-standard */
8889         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8890                                             ) {
8891       char *v = VariantName(gameInfo.variant);
8892       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8893         /* [HGM] in protocol 1 we have to assume all variants valid */
8894         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8895         DisplayFatalError(buf, 0, 1);
8896         return;
8897       }
8898
8899       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8900       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8901       if( gameInfo.variant == VariantXiangqi )
8902            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8903       if( gameInfo.variant == VariantShogi )
8904            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8905       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8906            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8907       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8908                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8909            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8910       if( gameInfo.variant == VariantCourier )
8911            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8912       if( gameInfo.variant == VariantSuper )
8913            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8914       if( gameInfo.variant == VariantGreat )
8915            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8916       if( gameInfo.variant == VariantSChess )
8917            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8918
8919       if(overruled) {
8920         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8921                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8922            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8923            if(StrStr(cps->variants, b) == NULL) {
8924                // specific sized variant not known, check if general sizing allowed
8925                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8926                    if(StrStr(cps->variants, "boardsize") == NULL) {
8927                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8928                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8929                        DisplayFatalError(buf, 0, 1);
8930                        return;
8931                    }
8932                    /* [HGM] here we really should compare with the maximum supported board size */
8933                }
8934            }
8935       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8936       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8937       SendToProgram(buf, cps);
8938     }
8939     currentlyInitializedVariant = gameInfo.variant;
8940
8941     /* [HGM] send opening position in FRC to first engine */
8942     if(setup) {
8943           SendToProgram("force\n", cps);
8944           SendBoard(cps, 0);
8945           /* engine is now in force mode! Set flag to wake it up after first move. */
8946           setboardSpoiledMachineBlack = 1;
8947     }
8948
8949     if (cps->sendICS) {
8950       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8951       SendToProgram(buf, cps);
8952     }
8953     cps->maybeThinking = FALSE;
8954     cps->offeredDraw = 0;
8955     if (!appData.icsActive) {
8956         SendTimeControl(cps, movesPerSession, timeControl,
8957                         timeIncrement, appData.searchDepth,
8958                         searchTime);
8959     }
8960     if (appData.showThinking
8961         // [HGM] thinking: four options require thinking output to be sent
8962         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8963                                 ) {
8964         SendToProgram("post\n", cps);
8965     }
8966     SendToProgram("hard\n", cps);
8967     if (!appData.ponderNextMove) {
8968         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8969            it without being sure what state we are in first.  "hard"
8970            is not a toggle, so that one is OK.
8971          */
8972         SendToProgram("easy\n", cps);
8973     }
8974     if (cps->usePing) {
8975       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8976       SendToProgram(buf, cps);
8977     }
8978     cps->initDone = TRUE;
8979 }
8980
8981
8982 void
8983 StartChessProgram(cps)
8984      ChessProgramState *cps;
8985 {
8986     char buf[MSG_SIZ];
8987     int err;
8988
8989     if (appData.noChessProgram) return;
8990     cps->initDone = FALSE;
8991
8992     if (strcmp(cps->host, "localhost") == 0) {
8993         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8994     } else if (*appData.remoteShell == NULLCHAR) {
8995         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8996     } else {
8997         if (*appData.remoteUser == NULLCHAR) {
8998           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8999                     cps->program);
9000         } else {
9001           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9002                     cps->host, appData.remoteUser, cps->program);
9003         }
9004         err = StartChildProcess(buf, "", &cps->pr);
9005     }
9006
9007     if (err != 0) {
9008       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9009         DisplayFatalError(buf, err, 1);
9010         cps->pr = NoProc;
9011         cps->isr = NULL;
9012         return;
9013     }
9014
9015     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9016     if (cps->protocolVersion > 1) {
9017       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9018       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9019       cps->comboCnt = 0;  //                and values of combo boxes
9020       SendToProgram(buf, cps);
9021     } else {
9022       SendToProgram("xboard\n", cps);
9023     }
9024 }
9025
9026
9027 void
9028 TwoMachinesEventIfReady P((void))
9029 {
9030   if (first.lastPing != first.lastPong) {
9031     DisplayMessage("", _("Waiting for first chess program"));
9032     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9033     return;
9034   }
9035   if (second.lastPing != second.lastPong) {
9036     DisplayMessage("", _("Waiting for second chess program"));
9037     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9038     return;
9039   }
9040   ThawUI();
9041   TwoMachinesEvent();
9042 }
9043
9044 void
9045 NextMatchGame P((void))
9046 {
9047     int index; /* [HGM] autoinc: step load index during match */
9048     Reset(FALSE, TRUE);
9049     if (*appData.loadGameFile != NULLCHAR) {
9050         index = appData.loadGameIndex;
9051         if(index < 0) { // [HGM] autoinc
9052             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9053             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9054         }
9055         LoadGameFromFile(appData.loadGameFile,
9056                          index,
9057                          appData.loadGameFile, FALSE);
9058     } else if (*appData.loadPositionFile != NULLCHAR) {
9059         index = appData.loadPositionIndex;
9060         if(index < 0) { // [HGM] autoinc
9061             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9062             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9063         }
9064         LoadPositionFromFile(appData.loadPositionFile,
9065                              index,
9066                              appData.loadPositionFile);
9067     }
9068     TwoMachinesEventIfReady();
9069 }
9070
9071 void UserAdjudicationEvent( int result )
9072 {
9073     ChessMove gameResult = GameIsDrawn;
9074
9075     if( result > 0 ) {
9076         gameResult = WhiteWins;
9077     }
9078     else if( result < 0 ) {
9079         gameResult = BlackWins;
9080     }
9081
9082     if( gameMode == TwoMachinesPlay ) {
9083         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9084     }
9085 }
9086
9087
9088 // [HGM] save: calculate checksum of game to make games easily identifiable
9089 int StringCheckSum(char *s)
9090 {
9091         int i = 0;
9092         if(s==NULL) return 0;
9093         while(*s) i = i*259 + *s++;
9094         return i;
9095 }
9096
9097 int GameCheckSum()
9098 {
9099         int i, sum=0;
9100         for(i=backwardMostMove; i<forwardMostMove; i++) {
9101                 sum += pvInfoList[i].depth;
9102                 sum += StringCheckSum(parseList[i]);
9103                 sum += StringCheckSum(commentList[i]);
9104                 sum *= 261;
9105         }
9106         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9107         return sum + StringCheckSum(commentList[i]);
9108 } // end of save patch
9109
9110 void
9111 GameEnds(result, resultDetails, whosays)
9112      ChessMove result;
9113      char *resultDetails;
9114      int whosays;
9115 {
9116     GameMode nextGameMode;
9117     int isIcsGame;
9118     char buf[MSG_SIZ], popupRequested = 0;
9119
9120     if(endingGame) return; /* [HGM] crash: forbid recursion */
9121     endingGame = 1;
9122     if(twoBoards) { // [HGM] dual: switch back to one board
9123         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9124         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9125     }
9126     if (appData.debugMode) {
9127       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9128               result, resultDetails ? resultDetails : "(null)", whosays);
9129     }
9130
9131     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9132
9133     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9134         /* If we are playing on ICS, the server decides when the
9135            game is over, but the engine can offer to draw, claim
9136            a draw, or resign.
9137          */
9138 #if ZIPPY
9139         if (appData.zippyPlay && first.initDone) {
9140             if (result == GameIsDrawn) {
9141                 /* In case draw still needs to be claimed */
9142                 SendToICS(ics_prefix);
9143                 SendToICS("draw\n");
9144             } else if (StrCaseStr(resultDetails, "resign")) {
9145                 SendToICS(ics_prefix);
9146                 SendToICS("resign\n");
9147             }
9148         }
9149 #endif
9150         endingGame = 0; /* [HGM] crash */
9151         return;
9152     }
9153
9154     /* If we're loading the game from a file, stop */
9155     if (whosays == GE_FILE) {
9156       (void) StopLoadGameTimer();
9157       gameFileFP = NULL;
9158     }
9159
9160     /* Cancel draw offers */
9161     first.offeredDraw = second.offeredDraw = 0;
9162
9163     /* If this is an ICS game, only ICS can really say it's done;
9164        if not, anyone can. */
9165     isIcsGame = (gameMode == IcsPlayingWhite ||
9166                  gameMode == IcsPlayingBlack ||
9167                  gameMode == IcsObserving    ||
9168                  gameMode == IcsExamining);
9169
9170     if (!isIcsGame || whosays == GE_ICS) {
9171         /* OK -- not an ICS game, or ICS said it was done */
9172         StopClocks();
9173         if (!isIcsGame && !appData.noChessProgram)
9174           SetUserThinkingEnables();
9175
9176         /* [HGM] if a machine claims the game end we verify this claim */
9177         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9178             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9179                 char claimer;
9180                 ChessMove trueResult = (ChessMove) -1;
9181
9182                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9183                                             first.twoMachinesColor[0] :
9184                                             second.twoMachinesColor[0] ;
9185
9186                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9187                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9188                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9189                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9190                 } else
9191                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9192                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9193                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9194                 } else
9195                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9196                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9197                 }
9198
9199                 // now verify win claims, but not in drop games, as we don't understand those yet
9200                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9201                                                  || gameInfo.variant == VariantGreat) &&
9202                     (result == WhiteWins && claimer == 'w' ||
9203                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9204                       if (appData.debugMode) {
9205                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9206                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9207                       }
9208                       if(result != trueResult) {
9209                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9210                               result = claimer == 'w' ? BlackWins : WhiteWins;
9211                               resultDetails = buf;
9212                       }
9213                 } else
9214                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9215                     && (forwardMostMove <= backwardMostMove ||
9216                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9217                         (claimer=='b')==(forwardMostMove&1))
9218                                                                                   ) {
9219                       /* [HGM] verify: draws that were not flagged are false claims */
9220                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9221                       result = claimer == 'w' ? BlackWins : WhiteWins;
9222                       resultDetails = buf;
9223                 }
9224                 /* (Claiming a loss is accepted no questions asked!) */
9225             }
9226             /* [HGM] bare: don't allow bare King to win */
9227             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9228                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9229                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9230                && result != GameIsDrawn)
9231             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9232                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9233                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9234                         if(p >= 0 && p <= (int)WhiteKing) k++;
9235                 }
9236                 if (appData.debugMode) {
9237                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9238                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9239                 }
9240                 if(k <= 1) {
9241                         result = GameIsDrawn;
9242                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9243                         resultDetails = buf;
9244                 }
9245             }
9246         }
9247
9248
9249         if(serverMoves != NULL && !loadFlag) { char c = '=';
9250             if(result==WhiteWins) c = '+';
9251             if(result==BlackWins) c = '-';
9252             if(resultDetails != NULL)
9253                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9254         }
9255         if (resultDetails != NULL) {
9256             gameInfo.result = result;
9257             gameInfo.resultDetails = StrSave(resultDetails);
9258
9259             /* display last move only if game was not loaded from file */
9260             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9261                 DisplayMove(currentMove - 1);
9262
9263             if (forwardMostMove != 0) {
9264                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9265                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9266                                                                 ) {
9267                     if (*appData.saveGameFile != NULLCHAR) {
9268                         SaveGameToFile(appData.saveGameFile, TRUE);
9269                     } else if (appData.autoSaveGames) {
9270                         AutoSaveGame();
9271                     }
9272                     if (*appData.savePositionFile != NULLCHAR) {
9273                         SavePositionToFile(appData.savePositionFile);
9274                     }
9275                 }
9276             }
9277
9278             /* Tell program how game ended in case it is learning */
9279             /* [HGM] Moved this to after saving the PGN, just in case */
9280             /* engine died and we got here through time loss. In that */
9281             /* case we will get a fatal error writing the pipe, which */
9282             /* would otherwise lose us the PGN.                       */
9283             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9284             /* output during GameEnds should never be fatal anymore   */
9285             if (gameMode == MachinePlaysWhite ||
9286                 gameMode == MachinePlaysBlack ||
9287                 gameMode == TwoMachinesPlay ||
9288                 gameMode == IcsPlayingWhite ||
9289                 gameMode == IcsPlayingBlack ||
9290                 gameMode == BeginningOfGame) {
9291                 char buf[MSG_SIZ];
9292                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9293                         resultDetails);
9294                 if (first.pr != NoProc) {
9295                     SendToProgram(buf, &first);
9296                 }
9297                 if (second.pr != NoProc &&
9298                     gameMode == TwoMachinesPlay) {
9299                     SendToProgram(buf, &second);
9300                 }
9301             }
9302         }
9303
9304         if (appData.icsActive) {
9305             if (appData.quietPlay &&
9306                 (gameMode == IcsPlayingWhite ||
9307                  gameMode == IcsPlayingBlack)) {
9308                 SendToICS(ics_prefix);
9309                 SendToICS("set shout 1\n");
9310             }
9311             nextGameMode = IcsIdle;
9312             ics_user_moved = FALSE;
9313             /* clean up premove.  It's ugly when the game has ended and the
9314              * premove highlights are still on the board.
9315              */
9316             if (gotPremove) {
9317               gotPremove = FALSE;
9318               ClearPremoveHighlights();
9319               DrawPosition(FALSE, boards[currentMove]);
9320             }
9321             if (whosays == GE_ICS) {
9322                 switch (result) {
9323                 case WhiteWins:
9324                     if (gameMode == IcsPlayingWhite)
9325                         PlayIcsWinSound();
9326                     else if(gameMode == IcsPlayingBlack)
9327                         PlayIcsLossSound();
9328                     break;
9329                 case BlackWins:
9330                     if (gameMode == IcsPlayingBlack)
9331                         PlayIcsWinSound();
9332                     else if(gameMode == IcsPlayingWhite)
9333                         PlayIcsLossSound();
9334                     break;
9335                 case GameIsDrawn:
9336                     PlayIcsDrawSound();
9337                     break;
9338                 default:
9339                     PlayIcsUnfinishedSound();
9340                 }
9341             }
9342         } else if (gameMode == EditGame ||
9343                    gameMode == PlayFromGameFile ||
9344                    gameMode == AnalyzeMode ||
9345                    gameMode == AnalyzeFile) {
9346             nextGameMode = gameMode;
9347         } else {
9348             nextGameMode = EndOfGame;
9349         }
9350         pausing = FALSE;
9351         ModeHighlight();
9352     } else {
9353         nextGameMode = gameMode;
9354     }
9355
9356     if (appData.noChessProgram) {
9357         gameMode = nextGameMode;
9358         ModeHighlight();
9359         endingGame = 0; /* [HGM] crash */
9360         return;
9361     }
9362
9363     if (first.reuse) {
9364         /* Put first chess program into idle state */
9365         if (first.pr != NoProc &&
9366             (gameMode == MachinePlaysWhite ||
9367              gameMode == MachinePlaysBlack ||
9368              gameMode == TwoMachinesPlay ||
9369              gameMode == IcsPlayingWhite ||
9370              gameMode == IcsPlayingBlack ||
9371              gameMode == BeginningOfGame)) {
9372             SendToProgram("force\n", &first);
9373             if (first.usePing) {
9374               char buf[MSG_SIZ];
9375               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9376               SendToProgram(buf, &first);
9377             }
9378         }
9379     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9380         /* Kill off first chess program */
9381         if (first.isr != NULL)
9382           RemoveInputSource(first.isr);
9383         first.isr = NULL;
9384
9385         if (first.pr != NoProc) {
9386             ExitAnalyzeMode();
9387             DoSleep( appData.delayBeforeQuit );
9388             SendToProgram("quit\n", &first);
9389             DoSleep( appData.delayAfterQuit );
9390             DestroyChildProcess(first.pr, first.useSigterm);
9391         }
9392         first.pr = NoProc;
9393     }
9394     if (second.reuse) {
9395         /* Put second chess program into idle state */
9396         if (second.pr != NoProc &&
9397             gameMode == TwoMachinesPlay) {
9398             SendToProgram("force\n", &second);
9399             if (second.usePing) {
9400               char buf[MSG_SIZ];
9401               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9402               SendToProgram(buf, &second);
9403             }
9404         }
9405     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9406         /* Kill off second chess program */
9407         if (second.isr != NULL)
9408           RemoveInputSource(second.isr);
9409         second.isr = NULL;
9410
9411         if (second.pr != NoProc) {
9412             DoSleep( appData.delayBeforeQuit );
9413             SendToProgram("quit\n", &second);
9414             DoSleep( appData.delayAfterQuit );
9415             DestroyChildProcess(second.pr, second.useSigterm);
9416         }
9417         second.pr = NoProc;
9418     }
9419
9420     if (matchMode && gameMode == TwoMachinesPlay) {
9421         switch (result) {
9422         case WhiteWins:
9423           if (first.twoMachinesColor[0] == 'w') {
9424             first.matchWins++;
9425           } else {
9426             second.matchWins++;
9427           }
9428           break;
9429         case BlackWins:
9430           if (first.twoMachinesColor[0] == 'b') {
9431             first.matchWins++;
9432           } else {
9433             second.matchWins++;
9434           }
9435           break;
9436         default:
9437           break;
9438         }
9439         if (matchGame < appData.matchGames) {
9440             char *tmp;
9441             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9442                 tmp = first.twoMachinesColor;
9443                 first.twoMachinesColor = second.twoMachinesColor;
9444                 second.twoMachinesColor = tmp;
9445             }
9446             gameMode = nextGameMode;
9447             matchGame++;
9448             if(appData.matchPause>10000 || appData.matchPause<10)
9449                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9450             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9451             endingGame = 0; /* [HGM] crash */
9452             return;
9453         } else {
9454             gameMode = nextGameMode;
9455             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9456                      first.tidy, second.tidy,
9457                      first.matchWins, second.matchWins,
9458                      appData.matchGames - (first.matchWins + second.matchWins));
9459             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9460             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9461                 first.twoMachinesColor = "black\n";
9462                 second.twoMachinesColor = "white\n";
9463             } else {
9464                 first.twoMachinesColor = "white\n";
9465                 second.twoMachinesColor = "black\n";
9466             }
9467         }
9468     }
9469     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9470         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9471       ExitAnalyzeMode();
9472     gameMode = nextGameMode;
9473     ModeHighlight();
9474     endingGame = 0;  /* [HGM] crash */
9475     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9476       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9477         matchMode = FALSE; appData.matchGames = matchGame = 0;
9478         DisplayNote(buf);
9479       }
9480     }
9481 }
9482
9483 /* Assumes program was just initialized (initString sent).
9484    Leaves program in force mode. */
9485 void
9486 FeedMovesToProgram(cps, upto)
9487      ChessProgramState *cps;
9488      int upto;
9489 {
9490     int i;
9491
9492     if (appData.debugMode)
9493       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9494               startedFromSetupPosition ? "position and " : "",
9495               backwardMostMove, upto, cps->which);
9496     if(currentlyInitializedVariant != gameInfo.variant) {
9497       char buf[MSG_SIZ];
9498         // [HGM] variantswitch: make engine aware of new variant
9499         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9500                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9501         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9502         SendToProgram(buf, cps);
9503         currentlyInitializedVariant = gameInfo.variant;
9504     }
9505     SendToProgram("force\n", cps);
9506     if (startedFromSetupPosition) {
9507         SendBoard(cps, backwardMostMove);
9508     if (appData.debugMode) {
9509         fprintf(debugFP, "feedMoves\n");
9510     }
9511     }
9512     for (i = backwardMostMove; i < upto; i++) {
9513         SendMoveToProgram(i, cps);
9514     }
9515 }
9516
9517
9518 void
9519 ResurrectChessProgram()
9520 {
9521      /* The chess program may have exited.
9522         If so, restart it and feed it all the moves made so far. */
9523
9524     if (appData.noChessProgram || first.pr != NoProc) return;
9525
9526     StartChessProgram(&first);
9527     InitChessProgram(&first, FALSE);
9528     FeedMovesToProgram(&first, currentMove);
9529
9530     if (!first.sendTime) {
9531         /* can't tell gnuchess what its clock should read,
9532            so we bow to its notion. */
9533         ResetClocks();
9534         timeRemaining[0][currentMove] = whiteTimeRemaining;
9535         timeRemaining[1][currentMove] = blackTimeRemaining;
9536     }
9537
9538     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9539                 appData.icsEngineAnalyze) && first.analysisSupport) {
9540       SendToProgram("analyze\n", &first);
9541       first.analyzing = TRUE;
9542     }
9543 }
9544
9545 /*
9546  * Button procedures
9547  */
9548 void
9549 Reset(redraw, init)
9550      int redraw, init;
9551 {
9552     int i;
9553
9554     if (appData.debugMode) {
9555         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9556                 redraw, init, gameMode);
9557     }
9558     CleanupTail(); // [HGM] vari: delete any stored variations
9559     pausing = pauseExamInvalid = FALSE;
9560     startedFromSetupPosition = blackPlaysFirst = FALSE;
9561     firstMove = TRUE;
9562     whiteFlag = blackFlag = FALSE;
9563     userOfferedDraw = FALSE;
9564     hintRequested = bookRequested = FALSE;
9565     first.maybeThinking = FALSE;
9566     second.maybeThinking = FALSE;
9567     first.bookSuspend = FALSE; // [HGM] book
9568     second.bookSuspend = FALSE;
9569     thinkOutput[0] = NULLCHAR;
9570     lastHint[0] = NULLCHAR;
9571     ClearGameInfo(&gameInfo);
9572     gameInfo.variant = StringToVariant(appData.variant);
9573     ics_user_moved = ics_clock_paused = FALSE;
9574     ics_getting_history = H_FALSE;
9575     ics_gamenum = -1;
9576     white_holding[0] = black_holding[0] = NULLCHAR;
9577     ClearProgramStats();
9578     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9579
9580     ResetFrontEnd();
9581     ClearHighlights();
9582     flipView = appData.flipView;
9583     ClearPremoveHighlights();
9584     gotPremove = FALSE;
9585     alarmSounded = FALSE;
9586
9587     GameEnds(EndOfFile, NULL, GE_PLAYER);
9588     if(appData.serverMovesName != NULL) {
9589         /* [HGM] prepare to make moves file for broadcasting */
9590         clock_t t = clock();
9591         if(serverMoves != NULL) fclose(serverMoves);
9592         serverMoves = fopen(appData.serverMovesName, "r");
9593         if(serverMoves != NULL) {
9594             fclose(serverMoves);
9595             /* delay 15 sec before overwriting, so all clients can see end */
9596             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9597         }
9598         serverMoves = fopen(appData.serverMovesName, "w");
9599     }
9600
9601     ExitAnalyzeMode();
9602     gameMode = BeginningOfGame;
9603     ModeHighlight();
9604     if(appData.icsActive) gameInfo.variant = VariantNormal;
9605     currentMove = forwardMostMove = backwardMostMove = 0;
9606     InitPosition(redraw);
9607     for (i = 0; i < MAX_MOVES; i++) {
9608         if (commentList[i] != NULL) {
9609             free(commentList[i]);
9610             commentList[i] = NULL;
9611         }
9612     }
9613     ResetClocks();
9614     timeRemaining[0][0] = whiteTimeRemaining;
9615     timeRemaining[1][0] = blackTimeRemaining;
9616     if (first.pr == NULL) {
9617         StartChessProgram(&first);
9618     }
9619     if (init) {
9620             InitChessProgram(&first, startedFromSetupPosition);
9621     }
9622     DisplayTitle("");
9623     DisplayMessage("", "");
9624     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9625     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9626 }
9627
9628 void
9629 AutoPlayGameLoop()
9630 {
9631     for (;;) {
9632         if (!AutoPlayOneMove())
9633           return;
9634         if (matchMode || appData.timeDelay == 0)
9635           continue;
9636         if (appData.timeDelay < 0)
9637           return;
9638         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9639         break;
9640     }
9641 }
9642
9643
9644 int
9645 AutoPlayOneMove()
9646 {
9647     int fromX, fromY, toX, toY;
9648
9649     if (appData.debugMode) {
9650       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9651     }
9652
9653     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9654       return FALSE;
9655
9656     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9657       pvInfoList[currentMove].depth = programStats.depth;
9658       pvInfoList[currentMove].score = programStats.score;
9659       pvInfoList[currentMove].time  = 0;
9660       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9661     }
9662
9663     if (currentMove >= forwardMostMove) {
9664       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9665       gameMode = EditGame;
9666       ModeHighlight();
9667
9668       /* [AS] Clear current move marker at the end of a game */
9669       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9670
9671       return FALSE;
9672     }
9673
9674     toX = moveList[currentMove][2] - AAA;
9675     toY = moveList[currentMove][3] - ONE;
9676
9677     if (moveList[currentMove][1] == '@') {
9678         if (appData.highlightLastMove) {
9679             SetHighlights(-1, -1, toX, toY);
9680         }
9681     } else {
9682         fromX = moveList[currentMove][0] - AAA;
9683         fromY = moveList[currentMove][1] - ONE;
9684
9685         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9686
9687         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9688
9689         if (appData.highlightLastMove) {
9690             SetHighlights(fromX, fromY, toX, toY);
9691         }
9692     }
9693     DisplayMove(currentMove);
9694     SendMoveToProgram(currentMove++, &first);
9695     DisplayBothClocks();
9696     DrawPosition(FALSE, boards[currentMove]);
9697     // [HGM] PV info: always display, routine tests if empty
9698     DisplayComment(currentMove - 1, commentList[currentMove]);
9699     return TRUE;
9700 }
9701
9702
9703 int
9704 LoadGameOneMove(readAhead)
9705      ChessMove readAhead;
9706 {
9707     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9708     char promoChar = NULLCHAR;
9709     ChessMove moveType;
9710     char move[MSG_SIZ];
9711     char *p, *q;
9712
9713     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9714         gameMode != AnalyzeMode && gameMode != Training) {
9715         gameFileFP = NULL;
9716         return FALSE;
9717     }
9718
9719     yyboardindex = forwardMostMove;
9720     if (readAhead != EndOfFile) {
9721       moveType = readAhead;
9722     } else {
9723       if (gameFileFP == NULL)
9724           return FALSE;
9725       moveType = (ChessMove) Myylex();
9726     }
9727
9728     done = FALSE;
9729     switch (moveType) {
9730       case Comment:
9731         if (appData.debugMode)
9732           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9733         p = yy_text;
9734
9735         /* append the comment but don't display it */
9736         AppendComment(currentMove, p, FALSE);
9737         return TRUE;
9738
9739       case WhiteCapturesEnPassant:
9740       case BlackCapturesEnPassant:
9741       case WhitePromotion:
9742       case BlackPromotion:
9743       case WhiteNonPromotion:
9744       case BlackNonPromotion:
9745       case NormalMove:
9746       case WhiteKingSideCastle:
9747       case WhiteQueenSideCastle:
9748       case BlackKingSideCastle:
9749       case BlackQueenSideCastle:
9750       case WhiteKingSideCastleWild:
9751       case WhiteQueenSideCastleWild:
9752       case BlackKingSideCastleWild:
9753       case BlackQueenSideCastleWild:
9754       /* PUSH Fabien */
9755       case WhiteHSideCastleFR:
9756       case WhiteASideCastleFR:
9757       case BlackHSideCastleFR:
9758       case BlackASideCastleFR:
9759       /* POP Fabien */
9760         if (appData.debugMode)
9761           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9762         fromX = currentMoveString[0] - AAA;
9763         fromY = currentMoveString[1] - ONE;
9764         toX = currentMoveString[2] - AAA;
9765         toY = currentMoveString[3] - ONE;
9766         promoChar = currentMoveString[4];
9767         break;
9768
9769       case WhiteDrop:
9770       case BlackDrop:
9771         if (appData.debugMode)
9772           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9773         fromX = moveType == WhiteDrop ?
9774           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9775         (int) CharToPiece(ToLower(currentMoveString[0]));
9776         fromY = DROP_RANK;
9777         toX = currentMoveString[2] - AAA;
9778         toY = currentMoveString[3] - ONE;
9779         break;
9780
9781       case WhiteWins:
9782       case BlackWins:
9783       case GameIsDrawn:
9784       case GameUnfinished:
9785         if (appData.debugMode)
9786           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9787         p = strchr(yy_text, '{');
9788         if (p == NULL) p = strchr(yy_text, '(');
9789         if (p == NULL) {
9790             p = yy_text;
9791             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9792         } else {
9793             q = strchr(p, *p == '{' ? '}' : ')');
9794             if (q != NULL) *q = NULLCHAR;
9795             p++;
9796         }
9797         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
9798         GameEnds(moveType, p, GE_FILE);
9799         done = TRUE;
9800         if (cmailMsgLoaded) {
9801             ClearHighlights();
9802             flipView = WhiteOnMove(currentMove);
9803             if (moveType == GameUnfinished) flipView = !flipView;
9804             if (appData.debugMode)
9805               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9806         }
9807         break;
9808
9809       case EndOfFile:
9810         if (appData.debugMode)
9811           fprintf(debugFP, "Parser hit end of file\n");
9812         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9813           case MT_NONE:
9814           case MT_CHECK:
9815             break;
9816           case MT_CHECKMATE:
9817           case MT_STAINMATE:
9818             if (WhiteOnMove(currentMove)) {
9819                 GameEnds(BlackWins, "Black mates", GE_FILE);
9820             } else {
9821                 GameEnds(WhiteWins, "White mates", GE_FILE);
9822             }
9823             break;
9824           case MT_STALEMATE:
9825             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9826             break;
9827         }
9828         done = TRUE;
9829         break;
9830
9831       case MoveNumberOne:
9832         if (lastLoadGameStart == GNUChessGame) {
9833             /* GNUChessGames have numbers, but they aren't move numbers */
9834             if (appData.debugMode)
9835               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9836                       yy_text, (int) moveType);
9837             return LoadGameOneMove(EndOfFile); /* tail recursion */
9838         }
9839         /* else fall thru */
9840
9841       case XBoardGame:
9842       case GNUChessGame:
9843       case PGNTag:
9844         /* Reached start of next game in file */
9845         if (appData.debugMode)
9846           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9847         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9848           case MT_NONE:
9849           case MT_CHECK:
9850             break;
9851           case MT_CHECKMATE:
9852           case MT_STAINMATE:
9853             if (WhiteOnMove(currentMove)) {
9854                 GameEnds(BlackWins, "Black mates", GE_FILE);
9855             } else {
9856                 GameEnds(WhiteWins, "White mates", GE_FILE);
9857             }
9858             break;
9859           case MT_STALEMATE:
9860             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9861             break;
9862         }
9863         done = TRUE;
9864         break;
9865
9866       case PositionDiagram:     /* should not happen; ignore */
9867       case ElapsedTime:         /* ignore */
9868       case NAG:                 /* ignore */
9869         if (appData.debugMode)
9870           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9871                   yy_text, (int) moveType);
9872         return LoadGameOneMove(EndOfFile); /* tail recursion */
9873
9874       case IllegalMove:
9875         if (appData.testLegality) {
9876             if (appData.debugMode)
9877               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9878             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9879                     (forwardMostMove / 2) + 1,
9880                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9881             DisplayError(move, 0);
9882             done = TRUE;
9883         } else {
9884             if (appData.debugMode)
9885               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9886                       yy_text, currentMoveString);
9887             fromX = currentMoveString[0] - AAA;
9888             fromY = currentMoveString[1] - ONE;
9889             toX = currentMoveString[2] - AAA;
9890             toY = currentMoveString[3] - ONE;
9891             promoChar = currentMoveString[4];
9892         }
9893         break;
9894
9895       case AmbiguousMove:
9896         if (appData.debugMode)
9897           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9898         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9899                 (forwardMostMove / 2) + 1,
9900                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9901         DisplayError(move, 0);
9902         done = TRUE;
9903         break;
9904
9905       default:
9906       case ImpossibleMove:
9907         if (appData.debugMode)
9908           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9909         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9910                 (forwardMostMove / 2) + 1,
9911                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9912         DisplayError(move, 0);
9913         done = TRUE;
9914         break;
9915     }
9916
9917     if (done) {
9918         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9919             DrawPosition(FALSE, boards[currentMove]);
9920             DisplayBothClocks();
9921             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9922               DisplayComment(currentMove - 1, commentList[currentMove]);
9923         }
9924         (void) StopLoadGameTimer();
9925         gameFileFP = NULL;
9926         cmailOldMove = forwardMostMove;
9927         return FALSE;
9928     } else {
9929         /* currentMoveString is set as a side-effect of yylex */
9930         strcat(currentMoveString, "\n");
9931         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9932
9933         thinkOutput[0] = NULLCHAR;
9934         MakeMove(fromX, fromY, toX, toY, promoChar);
9935         currentMove = forwardMostMove;
9936         return TRUE;
9937     }
9938 }
9939
9940 /* Load the nth game from the given file */
9941 int
9942 LoadGameFromFile(filename, n, title, useList)
9943      char *filename;
9944      int n;
9945      char *title;
9946      /*Boolean*/ int useList;
9947 {
9948     FILE *f;
9949     char buf[MSG_SIZ];
9950
9951     if (strcmp(filename, "-") == 0) {
9952         f = stdin;
9953         title = "stdin";
9954     } else {
9955         f = fopen(filename, "rb");
9956         if (f == NULL) {
9957           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9958             DisplayError(buf, errno);
9959             return FALSE;
9960         }
9961     }
9962     if (fseek(f, 0, 0) == -1) {
9963         /* f is not seekable; probably a pipe */
9964         useList = FALSE;
9965     }
9966     if (useList && n == 0) {
9967         int error = GameListBuild(f);
9968         if (error) {
9969             DisplayError(_("Cannot build game list"), error);
9970         } else if (!ListEmpty(&gameList) &&
9971                    ((ListGame *) gameList.tailPred)->number > 1) {
9972             GameListPopUp(f, title);
9973             return TRUE;
9974         }
9975         GameListDestroy();
9976         n = 1;
9977     }
9978     if (n == 0) n = 1;
9979     return LoadGame(f, n, title, FALSE);
9980 }
9981
9982
9983 void
9984 MakeRegisteredMove()
9985 {
9986     int fromX, fromY, toX, toY;
9987     char promoChar;
9988     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9989         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9990           case CMAIL_MOVE:
9991           case CMAIL_DRAW:
9992             if (appData.debugMode)
9993               fprintf(debugFP, "Restoring %s for game %d\n",
9994                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9995
9996             thinkOutput[0] = NULLCHAR;
9997             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9998             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9999             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10000             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10001             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10002             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10003             MakeMove(fromX, fromY, toX, toY, promoChar);
10004             ShowMove(fromX, fromY, toX, toY);
10005
10006             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10007               case MT_NONE:
10008               case MT_CHECK:
10009                 break;
10010
10011               case MT_CHECKMATE:
10012               case MT_STAINMATE:
10013                 if (WhiteOnMove(currentMove)) {
10014                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10015                 } else {
10016                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10017                 }
10018                 break;
10019
10020               case MT_STALEMATE:
10021                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10022                 break;
10023             }
10024
10025             break;
10026
10027           case CMAIL_RESIGN:
10028             if (WhiteOnMove(currentMove)) {
10029                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10030             } else {
10031                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10032             }
10033             break;
10034
10035           case CMAIL_ACCEPT:
10036             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10037             break;
10038
10039           default:
10040             break;
10041         }
10042     }
10043
10044     return;
10045 }
10046
10047 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10048 int
10049 CmailLoadGame(f, gameNumber, title, useList)
10050      FILE *f;
10051      int gameNumber;
10052      char *title;
10053      int useList;
10054 {
10055     int retVal;
10056
10057     if (gameNumber > nCmailGames) {
10058         DisplayError(_("No more games in this message"), 0);
10059         return FALSE;
10060     }
10061     if (f == lastLoadGameFP) {
10062         int offset = gameNumber - lastLoadGameNumber;
10063         if (offset == 0) {
10064             cmailMsg[0] = NULLCHAR;
10065             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10066                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10067                 nCmailMovesRegistered--;
10068             }
10069             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10070             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10071                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10072             }
10073         } else {
10074             if (! RegisterMove()) return FALSE;
10075         }
10076     }
10077
10078     retVal = LoadGame(f, gameNumber, title, useList);
10079
10080     /* Make move registered during previous look at this game, if any */
10081     MakeRegisteredMove();
10082
10083     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10084         commentList[currentMove]
10085           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10086         DisplayComment(currentMove - 1, commentList[currentMove]);
10087     }
10088
10089     return retVal;
10090 }
10091
10092 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10093 int
10094 ReloadGame(offset)
10095      int offset;
10096 {
10097     int gameNumber = lastLoadGameNumber + offset;
10098     if (lastLoadGameFP == NULL) {
10099         DisplayError(_("No game has been loaded yet"), 0);
10100         return FALSE;
10101     }
10102     if (gameNumber <= 0) {
10103         DisplayError(_("Can't back up any further"), 0);
10104         return FALSE;
10105     }
10106     if (cmailMsgLoaded) {
10107         return CmailLoadGame(lastLoadGameFP, gameNumber,
10108                              lastLoadGameTitle, lastLoadGameUseList);
10109     } else {
10110         return LoadGame(lastLoadGameFP, gameNumber,
10111                         lastLoadGameTitle, lastLoadGameUseList);
10112     }
10113 }
10114
10115
10116
10117 /* Load the nth game from open file f */
10118 int
10119 LoadGame(f, gameNumber, title, useList)
10120      FILE *f;
10121      int gameNumber;
10122      char *title;
10123      int useList;
10124 {
10125     ChessMove cm;
10126     char buf[MSG_SIZ];
10127     int gn = gameNumber;
10128     ListGame *lg = NULL;
10129     int numPGNTags = 0;
10130     int err;
10131     GameMode oldGameMode;
10132     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10133
10134     if (appData.debugMode)
10135         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10136
10137     if (gameMode == Training )
10138         SetTrainingModeOff();
10139
10140     oldGameMode = gameMode;
10141     if (gameMode != BeginningOfGame) {
10142       Reset(FALSE, TRUE);
10143     }
10144
10145     gameFileFP = f;
10146     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10147         fclose(lastLoadGameFP);
10148     }
10149
10150     if (useList) {
10151         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10152
10153         if (lg) {
10154             fseek(f, lg->offset, 0);
10155             GameListHighlight(gameNumber);
10156             gn = 1;
10157         }
10158         else {
10159             DisplayError(_("Game number out of range"), 0);
10160             return FALSE;
10161         }
10162     } else {
10163         GameListDestroy();
10164         if (fseek(f, 0, 0) == -1) {
10165             if (f == lastLoadGameFP ?
10166                 gameNumber == lastLoadGameNumber + 1 :
10167                 gameNumber == 1) {
10168                 gn = 1;
10169             } else {
10170                 DisplayError(_("Can't seek on game file"), 0);
10171                 return FALSE;
10172             }
10173         }
10174     }
10175     lastLoadGameFP = f;
10176     lastLoadGameNumber = gameNumber;
10177     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10178     lastLoadGameUseList = useList;
10179
10180     yynewfile(f);
10181
10182     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10183       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10184                 lg->gameInfo.black);
10185             DisplayTitle(buf);
10186     } else if (*title != NULLCHAR) {
10187         if (gameNumber > 1) {
10188           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10189             DisplayTitle(buf);
10190         } else {
10191             DisplayTitle(title);
10192         }
10193     }
10194
10195     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10196         gameMode = PlayFromGameFile;
10197         ModeHighlight();
10198     }
10199
10200     currentMove = forwardMostMove = backwardMostMove = 0;
10201     CopyBoard(boards[0], initialPosition);
10202     StopClocks();
10203
10204     /*
10205      * Skip the first gn-1 games in the file.
10206      * Also skip over anything that precedes an identifiable
10207      * start of game marker, to avoid being confused by
10208      * garbage at the start of the file.  Currently
10209      * recognized start of game markers are the move number "1",
10210      * the pattern "gnuchess .* game", the pattern
10211      * "^[#;%] [^ ]* game file", and a PGN tag block.
10212      * A game that starts with one of the latter two patterns
10213      * will also have a move number 1, possibly
10214      * following a position diagram.
10215      * 5-4-02: Let's try being more lenient and allowing a game to
10216      * start with an unnumbered move.  Does that break anything?
10217      */
10218     cm = lastLoadGameStart = EndOfFile;
10219     while (gn > 0) {
10220         yyboardindex = forwardMostMove;
10221         cm = (ChessMove) Myylex();
10222         switch (cm) {
10223           case EndOfFile:
10224             if (cmailMsgLoaded) {
10225                 nCmailGames = CMAIL_MAX_GAMES - gn;
10226             } else {
10227                 Reset(TRUE, TRUE);
10228                 DisplayError(_("Game not found in file"), 0);
10229             }
10230             return FALSE;
10231
10232           case GNUChessGame:
10233           case XBoardGame:
10234             gn--;
10235             lastLoadGameStart = cm;
10236             break;
10237
10238           case MoveNumberOne:
10239             switch (lastLoadGameStart) {
10240               case GNUChessGame:
10241               case XBoardGame:
10242               case PGNTag:
10243                 break;
10244               case MoveNumberOne:
10245               case EndOfFile:
10246                 gn--;           /* count this game */
10247                 lastLoadGameStart = cm;
10248                 break;
10249               default:
10250                 /* impossible */
10251                 break;
10252             }
10253             break;
10254
10255           case PGNTag:
10256             switch (lastLoadGameStart) {
10257               case GNUChessGame:
10258               case PGNTag:
10259               case MoveNumberOne:
10260               case EndOfFile:
10261                 gn--;           /* count this game */
10262                 lastLoadGameStart = cm;
10263                 break;
10264               case XBoardGame:
10265                 lastLoadGameStart = cm; /* game counted already */
10266                 break;
10267               default:
10268                 /* impossible */
10269                 break;
10270             }
10271             if (gn > 0) {
10272                 do {
10273                     yyboardindex = forwardMostMove;
10274                     cm = (ChessMove) Myylex();
10275                 } while (cm == PGNTag || cm == Comment);
10276             }
10277             break;
10278
10279           case WhiteWins:
10280           case BlackWins:
10281           case GameIsDrawn:
10282             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10283                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10284                     != CMAIL_OLD_RESULT) {
10285                     nCmailResults ++ ;
10286                     cmailResult[  CMAIL_MAX_GAMES
10287                                 - gn - 1] = CMAIL_OLD_RESULT;
10288                 }
10289             }
10290             break;
10291
10292           case NormalMove:
10293             /* Only a NormalMove can be at the start of a game
10294              * without a position diagram. */
10295             if (lastLoadGameStart == EndOfFile ) {
10296               gn--;
10297               lastLoadGameStart = MoveNumberOne;
10298             }
10299             break;
10300
10301           default:
10302             break;
10303         }
10304     }
10305
10306     if (appData.debugMode)
10307       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10308
10309     if (cm == XBoardGame) {
10310         /* Skip any header junk before position diagram and/or move 1 */
10311         for (;;) {
10312             yyboardindex = forwardMostMove;
10313             cm = (ChessMove) Myylex();
10314
10315             if (cm == EndOfFile ||
10316                 cm == GNUChessGame || cm == XBoardGame) {
10317                 /* Empty game; pretend end-of-file and handle later */
10318                 cm = EndOfFile;
10319                 break;
10320             }
10321
10322             if (cm == MoveNumberOne || cm == PositionDiagram ||
10323                 cm == PGNTag || cm == Comment)
10324               break;
10325         }
10326     } else if (cm == GNUChessGame) {
10327         if (gameInfo.event != NULL) {
10328             free(gameInfo.event);
10329         }
10330         gameInfo.event = StrSave(yy_text);
10331     }
10332
10333     startedFromSetupPosition = FALSE;
10334     while (cm == PGNTag) {
10335         if (appData.debugMode)
10336           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10337         err = ParsePGNTag(yy_text, &gameInfo);
10338         if (!err) numPGNTags++;
10339
10340         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10341         if(gameInfo.variant != oldVariant) {
10342             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10343             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10344             InitPosition(TRUE);
10345             oldVariant = gameInfo.variant;
10346             if (appData.debugMode)
10347               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10348         }
10349
10350
10351         if (gameInfo.fen != NULL) {
10352           Board initial_position;
10353           startedFromSetupPosition = TRUE;
10354           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10355             Reset(TRUE, TRUE);
10356             DisplayError(_("Bad FEN position in file"), 0);
10357             return FALSE;
10358           }
10359           CopyBoard(boards[0], initial_position);
10360           if (blackPlaysFirst) {
10361             currentMove = forwardMostMove = backwardMostMove = 1;
10362             CopyBoard(boards[1], initial_position);
10363             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10364             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10365             timeRemaining[0][1] = whiteTimeRemaining;
10366             timeRemaining[1][1] = blackTimeRemaining;
10367             if (commentList[0] != NULL) {
10368               commentList[1] = commentList[0];
10369               commentList[0] = NULL;
10370             }
10371           } else {
10372             currentMove = forwardMostMove = backwardMostMove = 0;
10373           }
10374           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10375           {   int i;
10376               initialRulePlies = FENrulePlies;
10377               for( i=0; i< nrCastlingRights; i++ )
10378                   initialRights[i] = initial_position[CASTLING][i];
10379           }
10380           yyboardindex = forwardMostMove;
10381           free(gameInfo.fen);
10382           gameInfo.fen = NULL;
10383         }
10384
10385         yyboardindex = forwardMostMove;
10386         cm = (ChessMove) Myylex();
10387
10388         /* Handle comments interspersed among the tags */
10389         while (cm == Comment) {
10390             char *p;
10391             if (appData.debugMode)
10392               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10393             p = yy_text;
10394             AppendComment(currentMove, p, FALSE);
10395             yyboardindex = forwardMostMove;
10396             cm = (ChessMove) Myylex();
10397         }
10398     }
10399
10400     /* don't rely on existence of Event tag since if game was
10401      * pasted from clipboard the Event tag may not exist
10402      */
10403     if (numPGNTags > 0){
10404         char *tags;
10405         if (gameInfo.variant == VariantNormal) {
10406           VariantClass v = StringToVariant(gameInfo.event);
10407           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10408           if(v < VariantShogi) gameInfo.variant = v;
10409         }
10410         if (!matchMode) {
10411           if( appData.autoDisplayTags ) {
10412             tags = PGNTags(&gameInfo);
10413             TagsPopUp(tags, CmailMsg());
10414             free(tags);
10415           }
10416         }
10417     } else {
10418         /* Make something up, but don't display it now */
10419         SetGameInfo();
10420         TagsPopDown();
10421     }
10422
10423     if (cm == PositionDiagram) {
10424         int i, j;
10425         char *p;
10426         Board initial_position;
10427
10428         if (appData.debugMode)
10429           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10430
10431         if (!startedFromSetupPosition) {
10432             p = yy_text;
10433             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10434               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10435                 switch (*p) {
10436                   case '[':
10437                   case '-':
10438                   case ' ':
10439                   case '\t':
10440                   case '\n':
10441                   case '\r':
10442                     break;
10443                   default:
10444                     initial_position[i][j++] = CharToPiece(*p);
10445                     break;
10446                 }
10447             while (*p == ' ' || *p == '\t' ||
10448                    *p == '\n' || *p == '\r') p++;
10449
10450             if (strncmp(p, "black", strlen("black"))==0)
10451               blackPlaysFirst = TRUE;
10452             else
10453               blackPlaysFirst = FALSE;
10454             startedFromSetupPosition = TRUE;
10455
10456             CopyBoard(boards[0], initial_position);
10457             if (blackPlaysFirst) {
10458                 currentMove = forwardMostMove = backwardMostMove = 1;
10459                 CopyBoard(boards[1], initial_position);
10460                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10461                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10462                 timeRemaining[0][1] = whiteTimeRemaining;
10463                 timeRemaining[1][1] = blackTimeRemaining;
10464                 if (commentList[0] != NULL) {
10465                     commentList[1] = commentList[0];
10466                     commentList[0] = NULL;
10467                 }
10468             } else {
10469                 currentMove = forwardMostMove = backwardMostMove = 0;
10470             }
10471         }
10472         yyboardindex = forwardMostMove;
10473         cm = (ChessMove) Myylex();
10474     }
10475
10476     if (first.pr == NoProc) {
10477         StartChessProgram(&first);
10478     }
10479     InitChessProgram(&first, FALSE);
10480     SendToProgram("force\n", &first);
10481     if (startedFromSetupPosition) {
10482         SendBoard(&first, forwardMostMove);
10483     if (appData.debugMode) {
10484         fprintf(debugFP, "Load Game\n");
10485     }
10486         DisplayBothClocks();
10487     }
10488
10489     /* [HGM] server: flag to write setup moves in broadcast file as one */
10490     loadFlag = appData.suppressLoadMoves;
10491
10492     while (cm == Comment) {
10493         char *p;
10494         if (appData.debugMode)
10495           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10496         p = yy_text;
10497         AppendComment(currentMove, p, FALSE);
10498         yyboardindex = forwardMostMove;
10499         cm = (ChessMove) Myylex();
10500     }
10501
10502     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10503         cm == WhiteWins || cm == BlackWins ||
10504         cm == GameIsDrawn || cm == GameUnfinished) {
10505         DisplayMessage("", _("No moves in game"));
10506         if (cmailMsgLoaded) {
10507             if (appData.debugMode)
10508               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10509             ClearHighlights();
10510             flipView = FALSE;
10511         }
10512         DrawPosition(FALSE, boards[currentMove]);
10513         DisplayBothClocks();
10514         gameMode = EditGame;
10515         ModeHighlight();
10516         gameFileFP = NULL;
10517         cmailOldMove = 0;
10518         return TRUE;
10519     }
10520
10521     // [HGM] PV info: routine tests if comment empty
10522     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10523         DisplayComment(currentMove - 1, commentList[currentMove]);
10524     }
10525     if (!matchMode && appData.timeDelay != 0)
10526       DrawPosition(FALSE, boards[currentMove]);
10527
10528     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10529       programStats.ok_to_send = 1;
10530     }
10531
10532     /* if the first token after the PGN tags is a move
10533      * and not move number 1, retrieve it from the parser
10534      */
10535     if (cm != MoveNumberOne)
10536         LoadGameOneMove(cm);
10537
10538     /* load the remaining moves from the file */
10539     while (LoadGameOneMove(EndOfFile)) {
10540       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10541       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10542     }
10543
10544     /* rewind to the start of the game */
10545     currentMove = backwardMostMove;
10546
10547     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10548
10549     if (oldGameMode == AnalyzeFile ||
10550         oldGameMode == AnalyzeMode) {
10551       AnalyzeFileEvent();
10552     }
10553
10554     if (matchMode || appData.timeDelay == 0) {
10555       ToEndEvent();
10556       gameMode = EditGame;
10557       ModeHighlight();
10558     } else if (appData.timeDelay > 0) {
10559       AutoPlayGameLoop();
10560     }
10561
10562     if (appData.debugMode)
10563         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10564
10565     loadFlag = 0; /* [HGM] true game starts */
10566     return TRUE;
10567 }
10568
10569 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10570 int
10571 ReloadPosition(offset)
10572      int offset;
10573 {
10574     int positionNumber = lastLoadPositionNumber + offset;
10575     if (lastLoadPositionFP == NULL) {
10576         DisplayError(_("No position has been loaded yet"), 0);
10577         return FALSE;
10578     }
10579     if (positionNumber <= 0) {
10580         DisplayError(_("Can't back up any further"), 0);
10581         return FALSE;
10582     }
10583     return LoadPosition(lastLoadPositionFP, positionNumber,
10584                         lastLoadPositionTitle);
10585 }
10586
10587 /* Load the nth position from the given file */
10588 int
10589 LoadPositionFromFile(filename, n, title)
10590      char *filename;
10591      int n;
10592      char *title;
10593 {
10594     FILE *f;
10595     char buf[MSG_SIZ];
10596
10597     if (strcmp(filename, "-") == 0) {
10598         return LoadPosition(stdin, n, "stdin");
10599     } else {
10600         f = fopen(filename, "rb");
10601         if (f == NULL) {
10602             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10603             DisplayError(buf, errno);
10604             return FALSE;
10605         } else {
10606             return LoadPosition(f, n, title);
10607         }
10608     }
10609 }
10610
10611 /* Load the nth position from the given open file, and close it */
10612 int
10613 LoadPosition(f, positionNumber, title)
10614      FILE *f;
10615      int positionNumber;
10616      char *title;
10617 {
10618     char *p, line[MSG_SIZ];
10619     Board initial_position;
10620     int i, j, fenMode, pn;
10621
10622     if (gameMode == Training )
10623         SetTrainingModeOff();
10624
10625     if (gameMode != BeginningOfGame) {
10626         Reset(FALSE, TRUE);
10627     }
10628     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10629         fclose(lastLoadPositionFP);
10630     }
10631     if (positionNumber == 0) positionNumber = 1;
10632     lastLoadPositionFP = f;
10633     lastLoadPositionNumber = positionNumber;
10634     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10635     if (first.pr == NoProc) {
10636       StartChessProgram(&first);
10637       InitChessProgram(&first, FALSE);
10638     }
10639     pn = positionNumber;
10640     if (positionNumber < 0) {
10641         /* Negative position number means to seek to that byte offset */
10642         if (fseek(f, -positionNumber, 0) == -1) {
10643             DisplayError(_("Can't seek on position file"), 0);
10644             return FALSE;
10645         };
10646         pn = 1;
10647     } else {
10648         if (fseek(f, 0, 0) == -1) {
10649             if (f == lastLoadPositionFP ?
10650                 positionNumber == lastLoadPositionNumber + 1 :
10651                 positionNumber == 1) {
10652                 pn = 1;
10653             } else {
10654                 DisplayError(_("Can't seek on position file"), 0);
10655                 return FALSE;
10656             }
10657         }
10658     }
10659     /* See if this file is FEN or old-style xboard */
10660     if (fgets(line, MSG_SIZ, f) == NULL) {
10661         DisplayError(_("Position not found in file"), 0);
10662         return FALSE;
10663     }
10664     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10665     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10666
10667     if (pn >= 2) {
10668         if (fenMode || line[0] == '#') pn--;
10669         while (pn > 0) {
10670             /* skip positions before number pn */
10671             if (fgets(line, MSG_SIZ, f) == NULL) {
10672                 Reset(TRUE, TRUE);
10673                 DisplayError(_("Position not found in file"), 0);
10674                 return FALSE;
10675             }
10676             if (fenMode || line[0] == '#') pn--;
10677         }
10678     }
10679
10680     if (fenMode) {
10681         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10682             DisplayError(_("Bad FEN position in file"), 0);
10683             return FALSE;
10684         }
10685     } else {
10686         (void) fgets(line, MSG_SIZ, f);
10687         (void) fgets(line, MSG_SIZ, f);
10688
10689         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10690             (void) fgets(line, MSG_SIZ, f);
10691             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10692                 if (*p == ' ')
10693                   continue;
10694                 initial_position[i][j++] = CharToPiece(*p);
10695             }
10696         }
10697
10698         blackPlaysFirst = FALSE;
10699         if (!feof(f)) {
10700             (void) fgets(line, MSG_SIZ, f);
10701             if (strncmp(line, "black", strlen("black"))==0)
10702               blackPlaysFirst = TRUE;
10703         }
10704     }
10705     startedFromSetupPosition = TRUE;
10706
10707     SendToProgram("force\n", &first);
10708     CopyBoard(boards[0], initial_position);
10709     if (blackPlaysFirst) {
10710         currentMove = forwardMostMove = backwardMostMove = 1;
10711         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10712         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10713         CopyBoard(boards[1], initial_position);
10714         DisplayMessage("", _("Black to play"));
10715     } else {
10716         currentMove = forwardMostMove = backwardMostMove = 0;
10717         DisplayMessage("", _("White to play"));
10718     }
10719     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10720     SendBoard(&first, forwardMostMove);
10721     if (appData.debugMode) {
10722 int i, j;
10723   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10724   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10725         fprintf(debugFP, "Load Position\n");
10726     }
10727
10728     if (positionNumber > 1) {
10729       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10730         DisplayTitle(line);
10731     } else {
10732         DisplayTitle(title);
10733     }
10734     gameMode = EditGame;
10735     ModeHighlight();
10736     ResetClocks();
10737     timeRemaining[0][1] = whiteTimeRemaining;
10738     timeRemaining[1][1] = blackTimeRemaining;
10739     DrawPosition(FALSE, boards[currentMove]);
10740
10741     return TRUE;
10742 }
10743
10744
10745 void
10746 CopyPlayerNameIntoFileName(dest, src)
10747      char **dest, *src;
10748 {
10749     while (*src != NULLCHAR && *src != ',') {
10750         if (*src == ' ') {
10751             *(*dest)++ = '_';
10752             src++;
10753         } else {
10754             *(*dest)++ = *src++;
10755         }
10756     }
10757 }
10758
10759 char *DefaultFileName(ext)
10760      char *ext;
10761 {
10762     static char def[MSG_SIZ];
10763     char *p;
10764
10765     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10766         p = def;
10767         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10768         *p++ = '-';
10769         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10770         *p++ = '.';
10771         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10772     } else {
10773         def[0] = NULLCHAR;
10774     }
10775     return def;
10776 }
10777
10778 /* Save the current game to the given file */
10779 int
10780 SaveGameToFile(filename, append)
10781      char *filename;
10782      int append;
10783 {
10784     FILE *f;
10785     char buf[MSG_SIZ];
10786
10787     if (strcmp(filename, "-") == 0) {
10788         return SaveGame(stdout, 0, NULL);
10789     } else {
10790         f = fopen(filename, append ? "a" : "w");
10791         if (f == NULL) {
10792             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10793             DisplayError(buf, errno);
10794             return FALSE;
10795         } else {
10796             return SaveGame(f, 0, NULL);
10797         }
10798     }
10799 }
10800
10801 char *
10802 SavePart(str)
10803      char *str;
10804 {
10805     static char buf[MSG_SIZ];
10806     char *p;
10807
10808     p = strchr(str, ' ');
10809     if (p == NULL) return str;
10810     strncpy(buf, str, p - str);
10811     buf[p - str] = NULLCHAR;
10812     return buf;
10813 }
10814
10815 #define PGN_MAX_LINE 75
10816
10817 #define PGN_SIDE_WHITE  0
10818 #define PGN_SIDE_BLACK  1
10819
10820 /* [AS] */
10821 static int FindFirstMoveOutOfBook( int side )
10822 {
10823     int result = -1;
10824
10825     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10826         int index = backwardMostMove;
10827         int has_book_hit = 0;
10828
10829         if( (index % 2) != side ) {
10830             index++;
10831         }
10832
10833         while( index < forwardMostMove ) {
10834             /* Check to see if engine is in book */
10835             int depth = pvInfoList[index].depth;
10836             int score = pvInfoList[index].score;
10837             int in_book = 0;
10838
10839             if( depth <= 2 ) {
10840                 in_book = 1;
10841             }
10842             else if( score == 0 && depth == 63 ) {
10843                 in_book = 1; /* Zappa */
10844             }
10845             else if( score == 2 && depth == 99 ) {
10846                 in_book = 1; /* Abrok */
10847             }
10848
10849             has_book_hit += in_book;
10850
10851             if( ! in_book ) {
10852                 result = index;
10853
10854                 break;
10855             }
10856
10857             index += 2;
10858         }
10859     }
10860
10861     return result;
10862 }
10863
10864 /* [AS] */
10865 void GetOutOfBookInfo( char * buf )
10866 {
10867     int oob[2];
10868     int i;
10869     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10870
10871     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10872     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10873
10874     *buf = '\0';
10875
10876     if( oob[0] >= 0 || oob[1] >= 0 ) {
10877         for( i=0; i<2; i++ ) {
10878             int idx = oob[i];
10879
10880             if( idx >= 0 ) {
10881                 if( i > 0 && oob[0] >= 0 ) {
10882                     strcat( buf, "   " );
10883                 }
10884
10885                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10886                 sprintf( buf+strlen(buf), "%s%.2f",
10887                     pvInfoList[idx].score >= 0 ? "+" : "",
10888                     pvInfoList[idx].score / 100.0 );
10889             }
10890         }
10891     }
10892 }
10893
10894 /* Save game in PGN style and close the file */
10895 int
10896 SaveGamePGN(f)
10897      FILE *f;
10898 {
10899     int i, offset, linelen, newblock;
10900     time_t tm;
10901 //    char *movetext;
10902     char numtext[32];
10903     int movelen, numlen, blank;
10904     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10905
10906     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10907
10908     tm = time((time_t *) NULL);
10909
10910     PrintPGNTags(f, &gameInfo);
10911
10912     if (backwardMostMove > 0 || startedFromSetupPosition) {
10913         char *fen = PositionToFEN(backwardMostMove, NULL);
10914         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10915         fprintf(f, "\n{--------------\n");
10916         PrintPosition(f, backwardMostMove);
10917         fprintf(f, "--------------}\n");
10918         free(fen);
10919     }
10920     else {
10921         /* [AS] Out of book annotation */
10922         if( appData.saveOutOfBookInfo ) {
10923             char buf[64];
10924
10925             GetOutOfBookInfo( buf );
10926
10927             if( buf[0] != '\0' ) {
10928                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10929             }
10930         }
10931
10932         fprintf(f, "\n");
10933     }
10934
10935     i = backwardMostMove;
10936     linelen = 0;
10937     newblock = TRUE;
10938
10939     while (i < forwardMostMove) {
10940         /* Print comments preceding this move */
10941         if (commentList[i] != NULL) {
10942             if (linelen > 0) fprintf(f, "\n");
10943             fprintf(f, "%s", commentList[i]);
10944             linelen = 0;
10945             newblock = TRUE;
10946         }
10947
10948         /* Format move number */
10949         if ((i % 2) == 0)
10950           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10951         else
10952           if (newblock)
10953             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10954           else
10955             numtext[0] = NULLCHAR;
10956
10957         numlen = strlen(numtext);
10958         newblock = FALSE;
10959
10960         /* Print move number */
10961         blank = linelen > 0 && numlen > 0;
10962         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10963             fprintf(f, "\n");
10964             linelen = 0;
10965             blank = 0;
10966         }
10967         if (blank) {
10968             fprintf(f, " ");
10969             linelen++;
10970         }
10971         fprintf(f, "%s", numtext);
10972         linelen += numlen;
10973
10974         /* Get move */
10975         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10976         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10977
10978         /* Print move */
10979         blank = linelen > 0 && movelen > 0;
10980         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10981             fprintf(f, "\n");
10982             linelen = 0;
10983             blank = 0;
10984         }
10985         if (blank) {
10986             fprintf(f, " ");
10987             linelen++;
10988         }
10989         fprintf(f, "%s", move_buffer);
10990         linelen += movelen;
10991
10992         /* [AS] Add PV info if present */
10993         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10994             /* [HGM] add time */
10995             char buf[MSG_SIZ]; int seconds;
10996
10997             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10998
10999             if( seconds <= 0)
11000               buf[0] = 0;
11001             else
11002               if( seconds < 30 )
11003                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11004               else
11005                 {
11006                   seconds = (seconds + 4)/10; // round to full seconds
11007                   if( seconds < 60 )
11008                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11009                   else
11010                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11011                 }
11012
11013             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11014                       pvInfoList[i].score >= 0 ? "+" : "",
11015                       pvInfoList[i].score / 100.0,
11016                       pvInfoList[i].depth,
11017                       buf );
11018
11019             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11020
11021             /* Print score/depth */
11022             blank = linelen > 0 && movelen > 0;
11023             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11024                 fprintf(f, "\n");
11025                 linelen = 0;
11026                 blank = 0;
11027             }
11028             if (blank) {
11029                 fprintf(f, " ");
11030                 linelen++;
11031             }
11032             fprintf(f, "%s", move_buffer);
11033             linelen += movelen;
11034         }
11035
11036         i++;
11037     }
11038
11039     /* Start a new line */
11040     if (linelen > 0) fprintf(f, "\n");
11041
11042     /* Print comments after last move */
11043     if (commentList[i] != NULL) {
11044         fprintf(f, "%s\n", commentList[i]);
11045     }
11046
11047     /* Print result */
11048     if (gameInfo.resultDetails != NULL &&
11049         gameInfo.resultDetails[0] != NULLCHAR) {
11050         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11051                 PGNResult(gameInfo.result));
11052     } else {
11053         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11054     }
11055
11056     fclose(f);
11057     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11058     return TRUE;
11059 }
11060
11061 /* Save game in old style and close the file */
11062 int
11063 SaveGameOldStyle(f)
11064      FILE *f;
11065 {
11066     int i, offset;
11067     time_t tm;
11068
11069     tm = time((time_t *) NULL);
11070
11071     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11072     PrintOpponents(f);
11073
11074     if (backwardMostMove > 0 || startedFromSetupPosition) {
11075         fprintf(f, "\n[--------------\n");
11076         PrintPosition(f, backwardMostMove);
11077         fprintf(f, "--------------]\n");
11078     } else {
11079         fprintf(f, "\n");
11080     }
11081
11082     i = backwardMostMove;
11083     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11084
11085     while (i < forwardMostMove) {
11086         if (commentList[i] != NULL) {
11087             fprintf(f, "[%s]\n", commentList[i]);
11088         }
11089
11090         if ((i % 2) == 1) {
11091             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11092             i++;
11093         } else {
11094             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11095             i++;
11096             if (commentList[i] != NULL) {
11097                 fprintf(f, "\n");
11098                 continue;
11099             }
11100             if (i >= forwardMostMove) {
11101                 fprintf(f, "\n");
11102                 break;
11103             }
11104             fprintf(f, "%s\n", parseList[i]);
11105             i++;
11106         }
11107     }
11108
11109     if (commentList[i] != NULL) {
11110         fprintf(f, "[%s]\n", commentList[i]);
11111     }
11112
11113     /* This isn't really the old style, but it's close enough */
11114     if (gameInfo.resultDetails != NULL &&
11115         gameInfo.resultDetails[0] != NULLCHAR) {
11116         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11117                 gameInfo.resultDetails);
11118     } else {
11119         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11120     }
11121
11122     fclose(f);
11123     return TRUE;
11124 }
11125
11126 /* Save the current game to open file f and close the file */
11127 int
11128 SaveGame(f, dummy, dummy2)
11129      FILE *f;
11130      int dummy;
11131      char *dummy2;
11132 {
11133     if (gameMode == EditPosition) EditPositionDone(TRUE);
11134     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11135     if (appData.oldSaveStyle)
11136       return SaveGameOldStyle(f);
11137     else
11138       return SaveGamePGN(f);
11139 }
11140
11141 /* Save the current position to the given file */
11142 int
11143 SavePositionToFile(filename)
11144      char *filename;
11145 {
11146     FILE *f;
11147     char buf[MSG_SIZ];
11148
11149     if (strcmp(filename, "-") == 0) {
11150         return SavePosition(stdout, 0, NULL);
11151     } else {
11152         f = fopen(filename, "a");
11153         if (f == NULL) {
11154             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11155             DisplayError(buf, errno);
11156             return FALSE;
11157         } else {
11158             SavePosition(f, 0, NULL);
11159             return TRUE;
11160         }
11161     }
11162 }
11163
11164 /* Save the current position to the given open file and close the file */
11165 int
11166 SavePosition(f, dummy, dummy2)
11167      FILE *f;
11168      int dummy;
11169      char *dummy2;
11170 {
11171     time_t tm;
11172     char *fen;
11173
11174     if (gameMode == EditPosition) EditPositionDone(TRUE);
11175     if (appData.oldSaveStyle) {
11176         tm = time((time_t *) NULL);
11177
11178         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11179         PrintOpponents(f);
11180         fprintf(f, "[--------------\n");
11181         PrintPosition(f, currentMove);
11182         fprintf(f, "--------------]\n");
11183     } else {
11184         fen = PositionToFEN(currentMove, NULL);
11185         fprintf(f, "%s\n", fen);
11186         free(fen);
11187     }
11188     fclose(f);
11189     return TRUE;
11190 }
11191
11192 void
11193 ReloadCmailMsgEvent(unregister)
11194      int unregister;
11195 {
11196 #if !WIN32
11197     static char *inFilename = NULL;
11198     static char *outFilename;
11199     int i;
11200     struct stat inbuf, outbuf;
11201     int status;
11202
11203     /* Any registered moves are unregistered if unregister is set, */
11204     /* i.e. invoked by the signal handler */
11205     if (unregister) {
11206         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11207             cmailMoveRegistered[i] = FALSE;
11208             if (cmailCommentList[i] != NULL) {
11209                 free(cmailCommentList[i]);
11210                 cmailCommentList[i] = NULL;
11211             }
11212         }
11213         nCmailMovesRegistered = 0;
11214     }
11215
11216     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11217         cmailResult[i] = CMAIL_NOT_RESULT;
11218     }
11219     nCmailResults = 0;
11220
11221     if (inFilename == NULL) {
11222         /* Because the filenames are static they only get malloced once  */
11223         /* and they never get freed                                      */
11224         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11225         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11226
11227         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11228         sprintf(outFilename, "%s.out", appData.cmailGameName);
11229     }
11230
11231     status = stat(outFilename, &outbuf);
11232     if (status < 0) {
11233         cmailMailedMove = FALSE;
11234     } else {
11235         status = stat(inFilename, &inbuf);
11236         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11237     }
11238
11239     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11240        counts the games, notes how each one terminated, etc.
11241
11242        It would be nice to remove this kludge and instead gather all
11243        the information while building the game list.  (And to keep it
11244        in the game list nodes instead of having a bunch of fixed-size
11245        parallel arrays.)  Note this will require getting each game's
11246        termination from the PGN tags, as the game list builder does
11247        not process the game moves.  --mann
11248        */
11249     cmailMsgLoaded = TRUE;
11250     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11251
11252     /* Load first game in the file or popup game menu */
11253     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11254
11255 #endif /* !WIN32 */
11256     return;
11257 }
11258
11259 int
11260 RegisterMove()
11261 {
11262     FILE *f;
11263     char string[MSG_SIZ];
11264
11265     if (   cmailMailedMove
11266         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11267         return TRUE;            /* Allow free viewing  */
11268     }
11269
11270     /* Unregister move to ensure that we don't leave RegisterMove        */
11271     /* with the move registered when the conditions for registering no   */
11272     /* longer hold                                                       */
11273     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11274         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11275         nCmailMovesRegistered --;
11276
11277         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11278           {
11279               free(cmailCommentList[lastLoadGameNumber - 1]);
11280               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11281           }
11282     }
11283
11284     if (cmailOldMove == -1) {
11285         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11286         return FALSE;
11287     }
11288
11289     if (currentMove > cmailOldMove + 1) {
11290         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11291         return FALSE;
11292     }
11293
11294     if (currentMove < cmailOldMove) {
11295         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11296         return FALSE;
11297     }
11298
11299     if (forwardMostMove > currentMove) {
11300         /* Silently truncate extra moves */
11301         TruncateGame();
11302     }
11303
11304     if (   (currentMove == cmailOldMove + 1)
11305         || (   (currentMove == cmailOldMove)
11306             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11307                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11308         if (gameInfo.result != GameUnfinished) {
11309             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11310         }
11311
11312         if (commentList[currentMove] != NULL) {
11313             cmailCommentList[lastLoadGameNumber - 1]
11314               = StrSave(commentList[currentMove]);
11315         }
11316         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11317
11318         if (appData.debugMode)
11319           fprintf(debugFP, "Saving %s for game %d\n",
11320                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11321
11322         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11323
11324         f = fopen(string, "w");
11325         if (appData.oldSaveStyle) {
11326             SaveGameOldStyle(f); /* also closes the file */
11327
11328             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11329             f = fopen(string, "w");
11330             SavePosition(f, 0, NULL); /* also closes the file */
11331         } else {
11332             fprintf(f, "{--------------\n");
11333             PrintPosition(f, currentMove);
11334             fprintf(f, "--------------}\n\n");
11335
11336             SaveGame(f, 0, NULL); /* also closes the file*/
11337         }
11338
11339         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11340         nCmailMovesRegistered ++;
11341     } else if (nCmailGames == 1) {
11342         DisplayError(_("You have not made a move yet"), 0);
11343         return FALSE;
11344     }
11345
11346     return TRUE;
11347 }
11348
11349 void
11350 MailMoveEvent()
11351 {
11352 #if !WIN32
11353     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11354     FILE *commandOutput;
11355     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11356     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11357     int nBuffers;
11358     int i;
11359     int archived;
11360     char *arcDir;
11361
11362     if (! cmailMsgLoaded) {
11363         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11364         return;
11365     }
11366
11367     if (nCmailGames == nCmailResults) {
11368         DisplayError(_("No unfinished games"), 0);
11369         return;
11370     }
11371
11372 #if CMAIL_PROHIBIT_REMAIL
11373     if (cmailMailedMove) {
11374       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);
11375         DisplayError(msg, 0);
11376         return;
11377     }
11378 #endif
11379
11380     if (! (cmailMailedMove || RegisterMove())) return;
11381
11382     if (   cmailMailedMove
11383         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11384       snprintf(string, MSG_SIZ, partCommandString,
11385                appData.debugMode ? " -v" : "", appData.cmailGameName);
11386         commandOutput = popen(string, "r");
11387
11388         if (commandOutput == NULL) {
11389             DisplayError(_("Failed to invoke cmail"), 0);
11390         } else {
11391             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11392                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11393             }
11394             if (nBuffers > 1) {
11395                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11396                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11397                 nBytes = MSG_SIZ - 1;
11398             } else {
11399                 (void) memcpy(msg, buffer, nBytes);
11400             }
11401             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11402
11403             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11404                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11405
11406                 archived = TRUE;
11407                 for (i = 0; i < nCmailGames; i ++) {
11408                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11409                         archived = FALSE;
11410                     }
11411                 }
11412                 if (   archived
11413                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11414                         != NULL)) {
11415                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11416                            arcDir,
11417                            appData.cmailGameName,
11418                            gameInfo.date);
11419                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11420                     cmailMsgLoaded = FALSE;
11421                 }
11422             }
11423
11424             DisplayInformation(msg);
11425             pclose(commandOutput);
11426         }
11427     } else {
11428         if ((*cmailMsg) != '\0') {
11429             DisplayInformation(cmailMsg);
11430         }
11431     }
11432
11433     return;
11434 #endif /* !WIN32 */
11435 }
11436
11437 char *
11438 CmailMsg()
11439 {
11440 #if WIN32
11441     return NULL;
11442 #else
11443     int  prependComma = 0;
11444     char number[5];
11445     char string[MSG_SIZ];       /* Space for game-list */
11446     int  i;
11447
11448     if (!cmailMsgLoaded) return "";
11449
11450     if (cmailMailedMove) {
11451       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11452     } else {
11453         /* Create a list of games left */
11454       snprintf(string, MSG_SIZ, "[");
11455         for (i = 0; i < nCmailGames; i ++) {
11456             if (! (   cmailMoveRegistered[i]
11457                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11458                 if (prependComma) {
11459                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11460                 } else {
11461                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11462                     prependComma = 1;
11463                 }
11464
11465                 strcat(string, number);
11466             }
11467         }
11468         strcat(string, "]");
11469
11470         if (nCmailMovesRegistered + nCmailResults == 0) {
11471             switch (nCmailGames) {
11472               case 1:
11473                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11474                 break;
11475
11476               case 2:
11477                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11478                 break;
11479
11480               default:
11481                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11482                          nCmailGames);
11483                 break;
11484             }
11485         } else {
11486             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11487               case 1:
11488                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11489                          string);
11490                 break;
11491
11492               case 0:
11493                 if (nCmailResults == nCmailGames) {
11494                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11495                 } else {
11496                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11497                 }
11498                 break;
11499
11500               default:
11501                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11502                          string);
11503             }
11504         }
11505     }
11506     return cmailMsg;
11507 #endif /* WIN32 */
11508 }
11509
11510 void
11511 ResetGameEvent()
11512 {
11513     if (gameMode == Training)
11514       SetTrainingModeOff();
11515
11516     Reset(TRUE, TRUE);
11517     cmailMsgLoaded = FALSE;
11518     if (appData.icsActive) {
11519       SendToICS(ics_prefix);
11520       SendToICS("refresh\n");
11521     }
11522 }
11523
11524 void
11525 ExitEvent(status)
11526      int status;
11527 {
11528     exiting++;
11529     if (exiting > 2) {
11530       /* Give up on clean exit */
11531       exit(status);
11532     }
11533     if (exiting > 1) {
11534       /* Keep trying for clean exit */
11535       return;
11536     }
11537
11538     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11539
11540     if (telnetISR != NULL) {
11541       RemoveInputSource(telnetISR);
11542     }
11543     if (icsPR != NoProc) {
11544       DestroyChildProcess(icsPR, TRUE);
11545     }
11546
11547     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11548     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11549
11550     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11551     /* make sure this other one finishes before killing it!                  */
11552     if(endingGame) { int count = 0;
11553         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11554         while(endingGame && count++ < 10) DoSleep(1);
11555         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11556     }
11557
11558     /* Kill off chess programs */
11559     if (first.pr != NoProc) {
11560         ExitAnalyzeMode();
11561
11562         DoSleep( appData.delayBeforeQuit );
11563         SendToProgram("quit\n", &first);
11564         DoSleep( appData.delayAfterQuit );
11565         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11566     }
11567     if (second.pr != NoProc) {
11568         DoSleep( appData.delayBeforeQuit );
11569         SendToProgram("quit\n", &second);
11570         DoSleep( appData.delayAfterQuit );
11571         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11572     }
11573     if (first.isr != NULL) {
11574         RemoveInputSource(first.isr);
11575     }
11576     if (second.isr != NULL) {
11577         RemoveInputSource(second.isr);
11578     }
11579
11580     ShutDownFrontEnd();
11581     exit(status);
11582 }
11583
11584 void
11585 PauseEvent()
11586 {
11587     if (appData.debugMode)
11588         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11589     if (pausing) {
11590         pausing = FALSE;
11591         ModeHighlight();
11592         if (gameMode == MachinePlaysWhite ||
11593             gameMode == MachinePlaysBlack) {
11594             StartClocks();
11595         } else {
11596             DisplayBothClocks();
11597         }
11598         if (gameMode == PlayFromGameFile) {
11599             if (appData.timeDelay >= 0)
11600                 AutoPlayGameLoop();
11601         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11602             Reset(FALSE, TRUE);
11603             SendToICS(ics_prefix);
11604             SendToICS("refresh\n");
11605         } else if (currentMove < forwardMostMove) {
11606             ForwardInner(forwardMostMove);
11607         }
11608         pauseExamInvalid = FALSE;
11609     } else {
11610         switch (gameMode) {
11611           default:
11612             return;
11613           case IcsExamining:
11614             pauseExamForwardMostMove = forwardMostMove;
11615             pauseExamInvalid = FALSE;
11616             /* fall through */
11617           case IcsObserving:
11618           case IcsPlayingWhite:
11619           case IcsPlayingBlack:
11620             pausing = TRUE;
11621             ModeHighlight();
11622             return;
11623           case PlayFromGameFile:
11624             (void) StopLoadGameTimer();
11625             pausing = TRUE;
11626             ModeHighlight();
11627             break;
11628           case BeginningOfGame:
11629             if (appData.icsActive) return;
11630             /* else fall through */
11631           case MachinePlaysWhite:
11632           case MachinePlaysBlack:
11633           case TwoMachinesPlay:
11634             if (forwardMostMove == 0)
11635               return;           /* don't pause if no one has moved */
11636             if ((gameMode == MachinePlaysWhite &&
11637                  !WhiteOnMove(forwardMostMove)) ||
11638                 (gameMode == MachinePlaysBlack &&
11639                  WhiteOnMove(forwardMostMove))) {
11640                 StopClocks();
11641             }
11642             pausing = TRUE;
11643             ModeHighlight();
11644             break;
11645         }
11646     }
11647 }
11648
11649 void
11650 EditCommentEvent()
11651 {
11652     char title[MSG_SIZ];
11653
11654     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11655       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11656     } else {
11657       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11658                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11659                parseList[currentMove - 1]);
11660     }
11661
11662     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11663 }
11664
11665
11666 void
11667 EditTagsEvent()
11668 {
11669     char *tags = PGNTags(&gameInfo);
11670     EditTagsPopUp(tags, NULL);
11671     free(tags);
11672 }
11673
11674 void
11675 AnalyzeModeEvent()
11676 {
11677     if (appData.noChessProgram || gameMode == AnalyzeMode)
11678       return;
11679
11680     if (gameMode != AnalyzeFile) {
11681         if (!appData.icsEngineAnalyze) {
11682                EditGameEvent();
11683                if (gameMode != EditGame) return;
11684         }
11685         ResurrectChessProgram();
11686         SendToProgram("analyze\n", &first);
11687         first.analyzing = TRUE;
11688         /*first.maybeThinking = TRUE;*/
11689         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11690         EngineOutputPopUp();
11691     }
11692     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11693     pausing = FALSE;
11694     ModeHighlight();
11695     SetGameInfo();
11696
11697     StartAnalysisClock();
11698     GetTimeMark(&lastNodeCountTime);
11699     lastNodeCount = 0;
11700 }
11701
11702 void
11703 AnalyzeFileEvent()
11704 {
11705     if (appData.noChessProgram || gameMode == AnalyzeFile)
11706       return;
11707
11708     if (gameMode != AnalyzeMode) {
11709         EditGameEvent();
11710         if (gameMode != EditGame) return;
11711         ResurrectChessProgram();
11712         SendToProgram("analyze\n", &first);
11713         first.analyzing = TRUE;
11714         /*first.maybeThinking = TRUE;*/
11715         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11716         EngineOutputPopUp();
11717     }
11718     gameMode = AnalyzeFile;
11719     pausing = FALSE;
11720     ModeHighlight();
11721     SetGameInfo();
11722
11723     StartAnalysisClock();
11724     GetTimeMark(&lastNodeCountTime);
11725     lastNodeCount = 0;
11726 }
11727
11728 void
11729 MachineWhiteEvent()
11730 {
11731     char buf[MSG_SIZ];
11732     char *bookHit = NULL;
11733
11734     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11735       return;
11736
11737
11738     if (gameMode == PlayFromGameFile ||
11739         gameMode == TwoMachinesPlay  ||
11740         gameMode == Training         ||
11741         gameMode == AnalyzeMode      ||
11742         gameMode == EndOfGame)
11743         EditGameEvent();
11744
11745     if (gameMode == EditPosition)
11746         EditPositionDone(TRUE);
11747
11748     if (!WhiteOnMove(currentMove)) {
11749         DisplayError(_("It is not White's turn"), 0);
11750         return;
11751     }
11752
11753     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11754       ExitAnalyzeMode();
11755
11756     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11757         gameMode == AnalyzeFile)
11758         TruncateGame();
11759
11760     ResurrectChessProgram();    /* in case it isn't running */
11761     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11762         gameMode = MachinePlaysWhite;
11763         ResetClocks();
11764     } else
11765     gameMode = MachinePlaysWhite;
11766     pausing = FALSE;
11767     ModeHighlight();
11768     SetGameInfo();
11769     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11770     DisplayTitle(buf);
11771     if (first.sendName) {
11772       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11773       SendToProgram(buf, &first);
11774     }
11775     if (first.sendTime) {
11776       if (first.useColors) {
11777         SendToProgram("black\n", &first); /*gnu kludge*/
11778       }
11779       SendTimeRemaining(&first, TRUE);
11780     }
11781     if (first.useColors) {
11782       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11783     }
11784     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11785     SetMachineThinkingEnables();
11786     first.maybeThinking = TRUE;
11787     StartClocks();
11788     firstMove = FALSE;
11789
11790     if (appData.autoFlipView && !flipView) {
11791       flipView = !flipView;
11792       DrawPosition(FALSE, NULL);
11793       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11794     }
11795
11796     if(bookHit) { // [HGM] book: simulate book reply
11797         static char bookMove[MSG_SIZ]; // a bit generous?
11798
11799         programStats.nodes = programStats.depth = programStats.time =
11800         programStats.score = programStats.got_only_move = 0;
11801         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11802
11803         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11804         strcat(bookMove, bookHit);
11805         HandleMachineMove(bookMove, &first);
11806     }
11807 }
11808
11809 void
11810 MachineBlackEvent()
11811 {
11812   char buf[MSG_SIZ];
11813   char *bookHit = NULL;
11814
11815     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11816         return;
11817
11818
11819     if (gameMode == PlayFromGameFile ||
11820         gameMode == TwoMachinesPlay  ||
11821         gameMode == Training         ||
11822         gameMode == AnalyzeMode      ||
11823         gameMode == EndOfGame)
11824         EditGameEvent();
11825
11826     if (gameMode == EditPosition)
11827         EditPositionDone(TRUE);
11828
11829     if (WhiteOnMove(currentMove)) {
11830         DisplayError(_("It is not Black's turn"), 0);
11831         return;
11832     }
11833
11834     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11835       ExitAnalyzeMode();
11836
11837     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11838         gameMode == AnalyzeFile)
11839         TruncateGame();
11840
11841     ResurrectChessProgram();    /* in case it isn't running */
11842     gameMode = MachinePlaysBlack;
11843     pausing = FALSE;
11844     ModeHighlight();
11845     SetGameInfo();
11846     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11847     DisplayTitle(buf);
11848     if (first.sendName) {
11849       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11850       SendToProgram(buf, &first);
11851     }
11852     if (first.sendTime) {
11853       if (first.useColors) {
11854         SendToProgram("white\n", &first); /*gnu kludge*/
11855       }
11856       SendTimeRemaining(&first, FALSE);
11857     }
11858     if (first.useColors) {
11859       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11860     }
11861     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11862     SetMachineThinkingEnables();
11863     first.maybeThinking = TRUE;
11864     StartClocks();
11865
11866     if (appData.autoFlipView && flipView) {
11867       flipView = !flipView;
11868       DrawPosition(FALSE, NULL);
11869       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11870     }
11871     if(bookHit) { // [HGM] book: simulate book reply
11872         static char bookMove[MSG_SIZ]; // a bit generous?
11873
11874         programStats.nodes = programStats.depth = programStats.time =
11875         programStats.score = programStats.got_only_move = 0;
11876         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11877
11878         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11879         strcat(bookMove, bookHit);
11880         HandleMachineMove(bookMove, &first);
11881     }
11882 }
11883
11884
11885 void
11886 DisplayTwoMachinesTitle()
11887 {
11888     char buf[MSG_SIZ];
11889     if (appData.matchGames > 0) {
11890         if (first.twoMachinesColor[0] == 'w') {
11891           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11892                    gameInfo.white, gameInfo.black,
11893                    first.matchWins, second.matchWins,
11894                    matchGame - 1 - (first.matchWins + second.matchWins));
11895         } else {
11896           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11897                    gameInfo.white, gameInfo.black,
11898                    second.matchWins, first.matchWins,
11899                    matchGame - 1 - (first.matchWins + second.matchWins));
11900         }
11901     } else {
11902       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11903     }
11904     DisplayTitle(buf);
11905 }
11906
11907 void
11908 SettingsMenuIfReady()
11909 {
11910   if (second.lastPing != second.lastPong) {
11911     DisplayMessage("", _("Waiting for second chess program"));
11912     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11913     return;
11914   }
11915   ThawUI();
11916   DisplayMessage("", "");
11917   SettingsPopUp(&second);
11918 }
11919
11920 int
11921 WaitForSecond(DelayedEventCallback retry)
11922 {
11923     if (second.pr == NULL) {
11924         StartChessProgram(&second);
11925         if (second.protocolVersion == 1) {
11926           retry();
11927         } else {
11928           /* kludge: allow timeout for initial "feature" command */
11929           FreezeUI();
11930           DisplayMessage("", _("Starting second chess program"));
11931           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11932         }
11933         return 1;
11934     }
11935     return 0;
11936 }
11937
11938 void
11939 TwoMachinesEvent P((void))
11940 {
11941     int i;
11942     char buf[MSG_SIZ];
11943     ChessProgramState *onmove;
11944     char *bookHit = NULL;
11945
11946     if (appData.noChessProgram) return;
11947
11948     switch (gameMode) {
11949       case TwoMachinesPlay:
11950         return;
11951       case MachinePlaysWhite:
11952       case MachinePlaysBlack:
11953         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11954             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11955             return;
11956         }
11957         /* fall through */
11958       case BeginningOfGame:
11959       case PlayFromGameFile:
11960       case EndOfGame:
11961         EditGameEvent();
11962         if (gameMode != EditGame) return;
11963         break;
11964       case EditPosition:
11965         EditPositionDone(TRUE);
11966         break;
11967       case AnalyzeMode:
11968       case AnalyzeFile:
11969         ExitAnalyzeMode();
11970         break;
11971       case EditGame:
11972       default:
11973         break;
11974     }
11975
11976 //    forwardMostMove = currentMove;
11977     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11978     ResurrectChessProgram();    /* in case first program isn't running */
11979
11980     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11981     DisplayMessage("", "");
11982     InitChessProgram(&second, FALSE);
11983     SendToProgram("force\n", &second);
11984     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11985       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11986       return;
11987     }
11988     if (startedFromSetupPosition) {
11989         SendBoard(&second, backwardMostMove);
11990     if (appData.debugMode) {
11991         fprintf(debugFP, "Two Machines\n");
11992     }
11993     }
11994     for (i = backwardMostMove; i < forwardMostMove; i++) {
11995         SendMoveToProgram(i, &second);
11996     }
11997
11998     gameMode = TwoMachinesPlay;
11999     pausing = FALSE;
12000     ModeHighlight();
12001     SetGameInfo();
12002     DisplayTwoMachinesTitle();
12003     firstMove = TRUE;
12004     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12005         onmove = &first;
12006     } else {
12007         onmove = &second;
12008     }
12009
12010     SendToProgram(first.computerString, &first);
12011     if (first.sendName) {
12012       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12013       SendToProgram(buf, &first);
12014     }
12015     SendToProgram(second.computerString, &second);
12016     if (second.sendName) {
12017       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12018       SendToProgram(buf, &second);
12019     }
12020
12021     ResetClocks();
12022     if (!first.sendTime || !second.sendTime) {
12023         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12024         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12025     }
12026     if (onmove->sendTime) {
12027       if (onmove->useColors) {
12028         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12029       }
12030       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12031     }
12032     if (onmove->useColors) {
12033       SendToProgram(onmove->twoMachinesColor, onmove);
12034     }
12035     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12036 //    SendToProgram("go\n", onmove);
12037     onmove->maybeThinking = TRUE;
12038     SetMachineThinkingEnables();
12039
12040     StartClocks();
12041
12042     if(bookHit) { // [HGM] book: simulate book reply
12043         static char bookMove[MSG_SIZ]; // a bit generous?
12044
12045         programStats.nodes = programStats.depth = programStats.time =
12046         programStats.score = programStats.got_only_move = 0;
12047         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12048
12049         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12050         strcat(bookMove, bookHit);
12051         savedMessage = bookMove; // args for deferred call
12052         savedState = onmove;
12053         ScheduleDelayedEvent(DeferredBookMove, 1);
12054     }
12055 }
12056
12057 void
12058 TrainingEvent()
12059 {
12060     if (gameMode == Training) {
12061       SetTrainingModeOff();
12062       gameMode = PlayFromGameFile;
12063       DisplayMessage("", _("Training mode off"));
12064     } else {
12065       gameMode = Training;
12066       animateTraining = appData.animate;
12067
12068       /* make sure we are not already at the end of the game */
12069       if (currentMove < forwardMostMove) {
12070         SetTrainingModeOn();
12071         DisplayMessage("", _("Training mode on"));
12072       } else {
12073         gameMode = PlayFromGameFile;
12074         DisplayError(_("Already at end of game"), 0);
12075       }
12076     }
12077     ModeHighlight();
12078 }
12079
12080 void
12081 IcsClientEvent()
12082 {
12083     if (!appData.icsActive) return;
12084     switch (gameMode) {
12085       case IcsPlayingWhite:
12086       case IcsPlayingBlack:
12087       case IcsObserving:
12088       case IcsIdle:
12089       case BeginningOfGame:
12090       case IcsExamining:
12091         return;
12092
12093       case EditGame:
12094         break;
12095
12096       case EditPosition:
12097         EditPositionDone(TRUE);
12098         break;
12099
12100       case AnalyzeMode:
12101       case AnalyzeFile:
12102         ExitAnalyzeMode();
12103         break;
12104
12105       default:
12106         EditGameEvent();
12107         break;
12108     }
12109
12110     gameMode = IcsIdle;
12111     ModeHighlight();
12112     return;
12113 }
12114
12115
12116 void
12117 EditGameEvent()
12118 {
12119     int i;
12120
12121     switch (gameMode) {
12122       case Training:
12123         SetTrainingModeOff();
12124         break;
12125       case MachinePlaysWhite:
12126       case MachinePlaysBlack:
12127       case BeginningOfGame:
12128         SendToProgram("force\n", &first);
12129         SetUserThinkingEnables();
12130         break;
12131       case PlayFromGameFile:
12132         (void) StopLoadGameTimer();
12133         if (gameFileFP != NULL) {
12134             gameFileFP = NULL;
12135         }
12136         break;
12137       case EditPosition:
12138         EditPositionDone(TRUE);
12139         break;
12140       case AnalyzeMode:
12141       case AnalyzeFile:
12142         ExitAnalyzeMode();
12143         SendToProgram("force\n", &first);
12144         break;
12145       case TwoMachinesPlay:
12146         GameEnds(EndOfFile, NULL, GE_PLAYER);
12147         ResurrectChessProgram();
12148         SetUserThinkingEnables();
12149         break;
12150       case EndOfGame:
12151         ResurrectChessProgram();
12152         break;
12153       case IcsPlayingBlack:
12154       case IcsPlayingWhite:
12155         DisplayError(_("Warning: You are still playing a game"), 0);
12156         break;
12157       case IcsObserving:
12158         DisplayError(_("Warning: You are still observing a game"), 0);
12159         break;
12160       case IcsExamining:
12161         DisplayError(_("Warning: You are still examining a game"), 0);
12162         break;
12163       case IcsIdle:
12164         break;
12165       case EditGame:
12166       default:
12167         return;
12168     }
12169
12170     pausing = FALSE;
12171     StopClocks();
12172     first.offeredDraw = second.offeredDraw = 0;
12173
12174     if (gameMode == PlayFromGameFile) {
12175         whiteTimeRemaining = timeRemaining[0][currentMove];
12176         blackTimeRemaining = timeRemaining[1][currentMove];
12177         DisplayTitle("");
12178     }
12179
12180     if (gameMode == MachinePlaysWhite ||
12181         gameMode == MachinePlaysBlack ||
12182         gameMode == TwoMachinesPlay ||
12183         gameMode == EndOfGame) {
12184         i = forwardMostMove;
12185         while (i > currentMove) {
12186             SendToProgram("undo\n", &first);
12187             i--;
12188         }
12189         whiteTimeRemaining = timeRemaining[0][currentMove];
12190         blackTimeRemaining = timeRemaining[1][currentMove];
12191         DisplayBothClocks();
12192         if (whiteFlag || blackFlag) {
12193             whiteFlag = blackFlag = 0;
12194         }
12195         DisplayTitle("");
12196     }
12197
12198     gameMode = EditGame;
12199     ModeHighlight();
12200     SetGameInfo();
12201 }
12202
12203
12204 void
12205 EditPositionEvent()
12206 {
12207     if (gameMode == EditPosition) {
12208         EditGameEvent();
12209         return;
12210     }
12211
12212     EditGameEvent();
12213     if (gameMode != EditGame) return;
12214
12215     gameMode = EditPosition;
12216     ModeHighlight();
12217     SetGameInfo();
12218     if (currentMove > 0)
12219       CopyBoard(boards[0], boards[currentMove]);
12220
12221     blackPlaysFirst = !WhiteOnMove(currentMove);
12222     ResetClocks();
12223     currentMove = forwardMostMove = backwardMostMove = 0;
12224     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12225     DisplayMove(-1);
12226 }
12227
12228 void
12229 ExitAnalyzeMode()
12230 {
12231     /* [DM] icsEngineAnalyze - possible call from other functions */
12232     if (appData.icsEngineAnalyze) {
12233         appData.icsEngineAnalyze = FALSE;
12234
12235         DisplayMessage("",_("Close ICS engine analyze..."));
12236     }
12237     if (first.analysisSupport && first.analyzing) {
12238       SendToProgram("exit\n", &first);
12239       first.analyzing = FALSE;
12240     }
12241     thinkOutput[0] = NULLCHAR;
12242 }
12243
12244 void
12245 EditPositionDone(Boolean fakeRights)
12246 {
12247     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12248
12249     startedFromSetupPosition = TRUE;
12250     InitChessProgram(&first, FALSE);
12251     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12252       boards[0][EP_STATUS] = EP_NONE;
12253       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12254     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12255         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12256         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12257       } else boards[0][CASTLING][2] = NoRights;
12258     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12259         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12260         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12261       } else boards[0][CASTLING][5] = NoRights;
12262     }
12263     SendToProgram("force\n", &first);
12264     if (blackPlaysFirst) {
12265         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12266         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12267         currentMove = forwardMostMove = backwardMostMove = 1;
12268         CopyBoard(boards[1], boards[0]);
12269     } else {
12270         currentMove = forwardMostMove = backwardMostMove = 0;
12271     }
12272     SendBoard(&first, forwardMostMove);
12273     if (appData.debugMode) {
12274         fprintf(debugFP, "EditPosDone\n");
12275     }
12276     DisplayTitle("");
12277     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12278     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12279     gameMode = EditGame;
12280     ModeHighlight();
12281     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12282     ClearHighlights(); /* [AS] */
12283 }
12284
12285 /* Pause for `ms' milliseconds */
12286 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12287 void
12288 TimeDelay(ms)
12289      long ms;
12290 {
12291     TimeMark m1, m2;
12292
12293     GetTimeMark(&m1);
12294     do {
12295         GetTimeMark(&m2);
12296     } while (SubtractTimeMarks(&m2, &m1) < ms);
12297 }
12298
12299 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12300 void
12301 SendMultiLineToICS(buf)
12302      char *buf;
12303 {
12304     char temp[MSG_SIZ+1], *p;
12305     int len;
12306
12307     len = strlen(buf);
12308     if (len > MSG_SIZ)
12309       len = MSG_SIZ;
12310
12311     strncpy(temp, buf, len);
12312     temp[len] = 0;
12313
12314     p = temp;
12315     while (*p) {
12316         if (*p == '\n' || *p == '\r')
12317           *p = ' ';
12318         ++p;
12319     }
12320
12321     strcat(temp, "\n");
12322     SendToICS(temp);
12323     SendToPlayer(temp, strlen(temp));
12324 }
12325
12326 void
12327 SetWhiteToPlayEvent()
12328 {
12329     if (gameMode == EditPosition) {
12330         blackPlaysFirst = FALSE;
12331         DisplayBothClocks();    /* works because currentMove is 0 */
12332     } else if (gameMode == IcsExamining) {
12333         SendToICS(ics_prefix);
12334         SendToICS("tomove white\n");
12335     }
12336 }
12337
12338 void
12339 SetBlackToPlayEvent()
12340 {
12341     if (gameMode == EditPosition) {
12342         blackPlaysFirst = TRUE;
12343         currentMove = 1;        /* kludge */
12344         DisplayBothClocks();
12345         currentMove = 0;
12346     } else if (gameMode == IcsExamining) {
12347         SendToICS(ics_prefix);
12348         SendToICS("tomove black\n");
12349     }
12350 }
12351
12352 void
12353 EditPositionMenuEvent(selection, x, y)
12354      ChessSquare selection;
12355      int x, y;
12356 {
12357     char buf[MSG_SIZ];
12358     ChessSquare piece = boards[0][y][x];
12359
12360     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12361
12362     switch (selection) {
12363       case ClearBoard:
12364         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12365             SendToICS(ics_prefix);
12366             SendToICS("bsetup clear\n");
12367         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12368             SendToICS(ics_prefix);
12369             SendToICS("clearboard\n");
12370         } else {
12371             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12372                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12373                 for (y = 0; y < BOARD_HEIGHT; y++) {
12374                     if (gameMode == IcsExamining) {
12375                         if (boards[currentMove][y][x] != EmptySquare) {
12376                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12377                                     AAA + x, ONE + y);
12378                             SendToICS(buf);
12379                         }
12380                     } else {
12381                         boards[0][y][x] = p;
12382                     }
12383                 }
12384             }
12385         }
12386         if (gameMode == EditPosition) {
12387             DrawPosition(FALSE, boards[0]);
12388         }
12389         break;
12390
12391       case WhitePlay:
12392         SetWhiteToPlayEvent();
12393         break;
12394
12395       case BlackPlay:
12396         SetBlackToPlayEvent();
12397         break;
12398
12399       case EmptySquare:
12400         if (gameMode == IcsExamining) {
12401             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12402             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12403             SendToICS(buf);
12404         } else {
12405             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12406                 if(x == BOARD_LEFT-2) {
12407                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12408                     boards[0][y][1] = 0;
12409                 } else
12410                 if(x == BOARD_RGHT+1) {
12411                     if(y >= gameInfo.holdingsSize) break;
12412                     boards[0][y][BOARD_WIDTH-2] = 0;
12413                 } else break;
12414             }
12415             boards[0][y][x] = EmptySquare;
12416             DrawPosition(FALSE, boards[0]);
12417         }
12418         break;
12419
12420       case PromotePiece:
12421         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12422            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12423             selection = (ChessSquare) (PROMOTED piece);
12424         } else if(piece == EmptySquare) selection = WhiteSilver;
12425         else selection = (ChessSquare)((int)piece - 1);
12426         goto defaultlabel;
12427
12428       case DemotePiece:
12429         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12430            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12431             selection = (ChessSquare) (DEMOTED piece);
12432         } else if(piece == EmptySquare) selection = BlackSilver;
12433         else selection = (ChessSquare)((int)piece + 1);
12434         goto defaultlabel;
12435
12436       case WhiteQueen:
12437       case BlackQueen:
12438         if(gameInfo.variant == VariantShatranj ||
12439            gameInfo.variant == VariantXiangqi  ||
12440            gameInfo.variant == VariantCourier  ||
12441            gameInfo.variant == VariantMakruk     )
12442             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12443         goto defaultlabel;
12444
12445       case WhiteKing:
12446       case BlackKing:
12447         if(gameInfo.variant == VariantXiangqi)
12448             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12449         if(gameInfo.variant == VariantKnightmate)
12450             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12451       default:
12452         defaultlabel:
12453         if (gameMode == IcsExamining) {
12454             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12455             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12456                      PieceToChar(selection), AAA + x, ONE + y);
12457             SendToICS(buf);
12458         } else {
12459             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12460                 int n;
12461                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12462                     n = PieceToNumber(selection - BlackPawn);
12463                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12464                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12465                     boards[0][BOARD_HEIGHT-1-n][1]++;
12466                 } else
12467                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12468                     n = PieceToNumber(selection);
12469                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12470                     boards[0][n][BOARD_WIDTH-1] = selection;
12471                     boards[0][n][BOARD_WIDTH-2]++;
12472                 }
12473             } else
12474             boards[0][y][x] = selection;
12475             DrawPosition(TRUE, boards[0]);
12476         }
12477         break;
12478     }
12479 }
12480
12481
12482 void
12483 DropMenuEvent(selection, x, y)
12484      ChessSquare selection;
12485      int x, y;
12486 {
12487     ChessMove moveType;
12488
12489     switch (gameMode) {
12490       case IcsPlayingWhite:
12491       case MachinePlaysBlack:
12492         if (!WhiteOnMove(currentMove)) {
12493             DisplayMoveError(_("It is Black's turn"));
12494             return;
12495         }
12496         moveType = WhiteDrop;
12497         break;
12498       case IcsPlayingBlack:
12499       case MachinePlaysWhite:
12500         if (WhiteOnMove(currentMove)) {
12501             DisplayMoveError(_("It is White's turn"));
12502             return;
12503         }
12504         moveType = BlackDrop;
12505         break;
12506       case EditGame:
12507         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12508         break;
12509       default:
12510         return;
12511     }
12512
12513     if (moveType == BlackDrop && selection < BlackPawn) {
12514       selection = (ChessSquare) ((int) selection
12515                                  + (int) BlackPawn - (int) WhitePawn);
12516     }
12517     if (boards[currentMove][y][x] != EmptySquare) {
12518         DisplayMoveError(_("That square is occupied"));
12519         return;
12520     }
12521
12522     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12523 }
12524
12525 void
12526 AcceptEvent()
12527 {
12528     /* Accept a pending offer of any kind from opponent */
12529
12530     if (appData.icsActive) {
12531         SendToICS(ics_prefix);
12532         SendToICS("accept\n");
12533     } else if (cmailMsgLoaded) {
12534         if (currentMove == cmailOldMove &&
12535             commentList[cmailOldMove] != NULL &&
12536             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12537                    "Black offers a draw" : "White offers a draw")) {
12538             TruncateGame();
12539             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12540             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12541         } else {
12542             DisplayError(_("There is no pending offer on this move"), 0);
12543             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12544         }
12545     } else {
12546         /* Not used for offers from chess program */
12547     }
12548 }
12549
12550 void
12551 DeclineEvent()
12552 {
12553     /* Decline a pending offer of any kind from opponent */
12554
12555     if (appData.icsActive) {
12556         SendToICS(ics_prefix);
12557         SendToICS("decline\n");
12558     } else if (cmailMsgLoaded) {
12559         if (currentMove == cmailOldMove &&
12560             commentList[cmailOldMove] != NULL &&
12561             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12562                    "Black offers a draw" : "White offers a draw")) {
12563 #ifdef NOTDEF
12564             AppendComment(cmailOldMove, "Draw declined", TRUE);
12565             DisplayComment(cmailOldMove - 1, "Draw declined");
12566 #endif /*NOTDEF*/
12567         } else {
12568             DisplayError(_("There is no pending offer on this move"), 0);
12569         }
12570     } else {
12571         /* Not used for offers from chess program */
12572     }
12573 }
12574
12575 void
12576 RematchEvent()
12577 {
12578     /* Issue ICS rematch command */
12579     if (appData.icsActive) {
12580         SendToICS(ics_prefix);
12581         SendToICS("rematch\n");
12582     }
12583 }
12584
12585 void
12586 CallFlagEvent()
12587 {
12588     /* Call your opponent's flag (claim a win on time) */
12589     if (appData.icsActive) {
12590         SendToICS(ics_prefix);
12591         SendToICS("flag\n");
12592     } else {
12593         switch (gameMode) {
12594           default:
12595             return;
12596           case MachinePlaysWhite:
12597             if (whiteFlag) {
12598                 if (blackFlag)
12599                   GameEnds(GameIsDrawn, "Both players ran out of time",
12600                            GE_PLAYER);
12601                 else
12602                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12603             } else {
12604                 DisplayError(_("Your opponent is not out of time"), 0);
12605             }
12606             break;
12607           case MachinePlaysBlack:
12608             if (blackFlag) {
12609                 if (whiteFlag)
12610                   GameEnds(GameIsDrawn, "Both players ran out of time",
12611                            GE_PLAYER);
12612                 else
12613                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12614             } else {
12615                 DisplayError(_("Your opponent is not out of time"), 0);
12616             }
12617             break;
12618         }
12619     }
12620 }
12621
12622 void
12623 DrawEvent()
12624 {
12625     /* Offer draw or accept pending draw offer from opponent */
12626
12627     if (appData.icsActive) {
12628         /* Note: tournament rules require draw offers to be
12629            made after you make your move but before you punch
12630            your clock.  Currently ICS doesn't let you do that;
12631            instead, you immediately punch your clock after making
12632            a move, but you can offer a draw at any time. */
12633
12634         SendToICS(ics_prefix);
12635         SendToICS("draw\n");
12636         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12637     } else if (cmailMsgLoaded) {
12638         if (currentMove == cmailOldMove &&
12639             commentList[cmailOldMove] != NULL &&
12640             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12641                    "Black offers a draw" : "White offers a draw")) {
12642             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12643             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12644         } else if (currentMove == cmailOldMove + 1) {
12645             char *offer = WhiteOnMove(cmailOldMove) ?
12646               "White offers a draw" : "Black offers a draw";
12647             AppendComment(currentMove, offer, TRUE);
12648             DisplayComment(currentMove - 1, offer);
12649             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12650         } else {
12651             DisplayError(_("You must make your move before offering a draw"), 0);
12652             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12653         }
12654     } else if (first.offeredDraw) {
12655         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12656     } else {
12657         if (first.sendDrawOffers) {
12658             SendToProgram("draw\n", &first);
12659             userOfferedDraw = TRUE;
12660         }
12661     }
12662 }
12663
12664 void
12665 AdjournEvent()
12666 {
12667     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12668
12669     if (appData.icsActive) {
12670         SendToICS(ics_prefix);
12671         SendToICS("adjourn\n");
12672     } else {
12673         /* Currently GNU Chess doesn't offer or accept Adjourns */
12674     }
12675 }
12676
12677
12678 void
12679 AbortEvent()
12680 {
12681     /* Offer Abort or accept pending Abort offer from opponent */
12682
12683     if (appData.icsActive) {
12684         SendToICS(ics_prefix);
12685         SendToICS("abort\n");
12686     } else {
12687         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12688     }
12689 }
12690
12691 void
12692 ResignEvent()
12693 {
12694     /* Resign.  You can do this even if it's not your turn. */
12695
12696     if (appData.icsActive) {
12697         SendToICS(ics_prefix);
12698         SendToICS("resign\n");
12699     } else {
12700         switch (gameMode) {
12701           case MachinePlaysWhite:
12702             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12703             break;
12704           case MachinePlaysBlack:
12705             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12706             break;
12707           case EditGame:
12708             if (cmailMsgLoaded) {
12709                 TruncateGame();
12710                 if (WhiteOnMove(cmailOldMove)) {
12711                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12712                 } else {
12713                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12714                 }
12715                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12716             }
12717             break;
12718           default:
12719             break;
12720         }
12721     }
12722 }
12723
12724
12725 void
12726 StopObservingEvent()
12727 {
12728     /* Stop observing current games */
12729     SendToICS(ics_prefix);
12730     SendToICS("unobserve\n");
12731 }
12732
12733 void
12734 StopExaminingEvent()
12735 {
12736     /* Stop observing current game */
12737     SendToICS(ics_prefix);
12738     SendToICS("unexamine\n");
12739 }
12740
12741 void
12742 ForwardInner(target)
12743      int target;
12744 {
12745     int limit;
12746
12747     if (appData.debugMode)
12748         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12749                 target, currentMove, forwardMostMove);
12750
12751     if (gameMode == EditPosition)
12752       return;
12753
12754     if (gameMode == PlayFromGameFile && !pausing)
12755       PauseEvent();
12756
12757     if (gameMode == IcsExamining && pausing)
12758       limit = pauseExamForwardMostMove;
12759     else
12760       limit = forwardMostMove;
12761
12762     if (target > limit) target = limit;
12763
12764     if (target > 0 && moveList[target - 1][0]) {
12765         int fromX, fromY, toX, toY;
12766         toX = moveList[target - 1][2] - AAA;
12767         toY = moveList[target - 1][3] - ONE;
12768         if (moveList[target - 1][1] == '@') {
12769             if (appData.highlightLastMove) {
12770                 SetHighlights(-1, -1, toX, toY);
12771             }
12772         } else {
12773             fromX = moveList[target - 1][0] - AAA;
12774             fromY = moveList[target - 1][1] - ONE;
12775             if (target == currentMove + 1) {
12776                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12777             }
12778             if (appData.highlightLastMove) {
12779                 SetHighlights(fromX, fromY, toX, toY);
12780             }
12781         }
12782     }
12783     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12784         gameMode == Training || gameMode == PlayFromGameFile ||
12785         gameMode == AnalyzeFile) {
12786         while (currentMove < target) {
12787             SendMoveToProgram(currentMove++, &first);
12788         }
12789     } else {
12790         currentMove = target;
12791     }
12792
12793     if (gameMode == EditGame || gameMode == EndOfGame) {
12794         whiteTimeRemaining = timeRemaining[0][currentMove];
12795         blackTimeRemaining = timeRemaining[1][currentMove];
12796     }
12797     DisplayBothClocks();
12798     DisplayMove(currentMove - 1);
12799     DrawPosition(FALSE, boards[currentMove]);
12800     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12801     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12802         DisplayComment(currentMove - 1, commentList[currentMove]);
12803     }
12804 }
12805
12806
12807 void
12808 ForwardEvent()
12809 {
12810     if (gameMode == IcsExamining && !pausing) {
12811         SendToICS(ics_prefix);
12812         SendToICS("forward\n");
12813     } else {
12814         ForwardInner(currentMove + 1);
12815     }
12816 }
12817
12818 void
12819 ToEndEvent()
12820 {
12821     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12822         /* to optimze, we temporarily turn off analysis mode while we feed
12823          * the remaining moves to the engine. Otherwise we get analysis output
12824          * after each move.
12825          */
12826         if (first.analysisSupport) {
12827           SendToProgram("exit\nforce\n", &first);
12828           first.analyzing = FALSE;
12829         }
12830     }
12831
12832     if (gameMode == IcsExamining && !pausing) {
12833         SendToICS(ics_prefix);
12834         SendToICS("forward 999999\n");
12835     } else {
12836         ForwardInner(forwardMostMove);
12837     }
12838
12839     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12840         /* we have fed all the moves, so reactivate analysis mode */
12841         SendToProgram("analyze\n", &first);
12842         first.analyzing = TRUE;
12843         /*first.maybeThinking = TRUE;*/
12844         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12845     }
12846 }
12847
12848 void
12849 BackwardInner(target)
12850      int target;
12851 {
12852     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12853
12854     if (appData.debugMode)
12855         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12856                 target, currentMove, forwardMostMove);
12857
12858     if (gameMode == EditPosition) return;
12859     if (currentMove <= backwardMostMove) {
12860         ClearHighlights();
12861         DrawPosition(full_redraw, boards[currentMove]);
12862         return;
12863     }
12864     if (gameMode == PlayFromGameFile && !pausing)
12865       PauseEvent();
12866
12867     if (moveList[target][0]) {
12868         int fromX, fromY, toX, toY;
12869         toX = moveList[target][2] - AAA;
12870         toY = moveList[target][3] - ONE;
12871         if (moveList[target][1] == '@') {
12872             if (appData.highlightLastMove) {
12873                 SetHighlights(-1, -1, toX, toY);
12874             }
12875         } else {
12876             fromX = moveList[target][0] - AAA;
12877             fromY = moveList[target][1] - ONE;
12878             if (target == currentMove - 1) {
12879                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12880             }
12881             if (appData.highlightLastMove) {
12882                 SetHighlights(fromX, fromY, toX, toY);
12883             }
12884         }
12885     }
12886     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12887         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12888         while (currentMove > target) {
12889             SendToProgram("undo\n", &first);
12890             currentMove--;
12891         }
12892     } else {
12893         currentMove = target;
12894     }
12895
12896     if (gameMode == EditGame || gameMode == EndOfGame) {
12897         whiteTimeRemaining = timeRemaining[0][currentMove];
12898         blackTimeRemaining = timeRemaining[1][currentMove];
12899     }
12900     DisplayBothClocks();
12901     DisplayMove(currentMove - 1);
12902     DrawPosition(full_redraw, boards[currentMove]);
12903     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12904     // [HGM] PV info: routine tests if comment empty
12905     DisplayComment(currentMove - 1, commentList[currentMove]);
12906 }
12907
12908 void
12909 BackwardEvent()
12910 {
12911     if (gameMode == IcsExamining && !pausing) {
12912         SendToICS(ics_prefix);
12913         SendToICS("backward\n");
12914     } else {
12915         BackwardInner(currentMove - 1);
12916     }
12917 }
12918
12919 void
12920 ToStartEvent()
12921 {
12922     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12923         /* to optimize, we temporarily turn off analysis mode while we undo
12924          * all the moves. Otherwise we get analysis output after each undo.
12925          */
12926         if (first.analysisSupport) {
12927           SendToProgram("exit\nforce\n", &first);
12928           first.analyzing = FALSE;
12929         }
12930     }
12931
12932     if (gameMode == IcsExamining && !pausing) {
12933         SendToICS(ics_prefix);
12934         SendToICS("backward 999999\n");
12935     } else {
12936         BackwardInner(backwardMostMove);
12937     }
12938
12939     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12940         /* we have fed all the moves, so reactivate analysis mode */
12941         SendToProgram("analyze\n", &first);
12942         first.analyzing = TRUE;
12943         /*first.maybeThinking = TRUE;*/
12944         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12945     }
12946 }
12947
12948 void
12949 ToNrEvent(int to)
12950 {
12951   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12952   if (to >= forwardMostMove) to = forwardMostMove;
12953   if (to <= backwardMostMove) to = backwardMostMove;
12954   if (to < currentMove) {
12955     BackwardInner(to);
12956   } else {
12957     ForwardInner(to);
12958   }
12959 }
12960
12961 void
12962 RevertEvent(Boolean annotate)
12963 {
12964     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12965         return;
12966     }
12967     if (gameMode != IcsExamining) {
12968         DisplayError(_("You are not examining a game"), 0);
12969         return;
12970     }
12971     if (pausing) {
12972         DisplayError(_("You can't revert while pausing"), 0);
12973         return;
12974     }
12975     SendToICS(ics_prefix);
12976     SendToICS("revert\n");
12977 }
12978
12979 void
12980 RetractMoveEvent()
12981 {
12982     switch (gameMode) {
12983       case MachinePlaysWhite:
12984       case MachinePlaysBlack:
12985         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12986             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12987             return;
12988         }
12989         if (forwardMostMove < 2) return;
12990         currentMove = forwardMostMove = forwardMostMove - 2;
12991         whiteTimeRemaining = timeRemaining[0][currentMove];
12992         blackTimeRemaining = timeRemaining[1][currentMove];
12993         DisplayBothClocks();
12994         DisplayMove(currentMove - 1);
12995         ClearHighlights();/*!! could figure this out*/
12996         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12997         SendToProgram("remove\n", &first);
12998         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12999         break;
13000
13001       case BeginningOfGame:
13002       default:
13003         break;
13004
13005       case IcsPlayingWhite:
13006       case IcsPlayingBlack:
13007         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13008             SendToICS(ics_prefix);
13009             SendToICS("takeback 2\n");
13010         } else {
13011             SendToICS(ics_prefix);
13012             SendToICS("takeback 1\n");
13013         }
13014         break;
13015     }
13016 }
13017
13018 void
13019 MoveNowEvent()
13020 {
13021     ChessProgramState *cps;
13022
13023     switch (gameMode) {
13024       case MachinePlaysWhite:
13025         if (!WhiteOnMove(forwardMostMove)) {
13026             DisplayError(_("It is your turn"), 0);
13027             return;
13028         }
13029         cps = &first;
13030         break;
13031       case MachinePlaysBlack:
13032         if (WhiteOnMove(forwardMostMove)) {
13033             DisplayError(_("It is your turn"), 0);
13034             return;
13035         }
13036         cps = &first;
13037         break;
13038       case TwoMachinesPlay:
13039         if (WhiteOnMove(forwardMostMove) ==
13040             (first.twoMachinesColor[0] == 'w')) {
13041             cps = &first;
13042         } else {
13043             cps = &second;
13044         }
13045         break;
13046       case BeginningOfGame:
13047       default:
13048         return;
13049     }
13050     SendToProgram("?\n", cps);
13051 }
13052
13053 void
13054 TruncateGameEvent()
13055 {
13056     EditGameEvent();
13057     if (gameMode != EditGame) return;
13058     TruncateGame();
13059 }
13060
13061 void
13062 TruncateGame()
13063 {
13064     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13065     if (forwardMostMove > currentMove) {
13066         if (gameInfo.resultDetails != NULL) {
13067             free(gameInfo.resultDetails);
13068             gameInfo.resultDetails = NULL;
13069             gameInfo.result = GameUnfinished;
13070         }
13071         forwardMostMove = currentMove;
13072         HistorySet(parseList, backwardMostMove, forwardMostMove,
13073                    currentMove-1);
13074     }
13075 }
13076
13077 void
13078 HintEvent()
13079 {
13080     if (appData.noChessProgram) return;
13081     switch (gameMode) {
13082       case MachinePlaysWhite:
13083         if (WhiteOnMove(forwardMostMove)) {
13084             DisplayError(_("Wait until your turn"), 0);
13085             return;
13086         }
13087         break;
13088       case BeginningOfGame:
13089       case MachinePlaysBlack:
13090         if (!WhiteOnMove(forwardMostMove)) {
13091             DisplayError(_("Wait until your turn"), 0);
13092             return;
13093         }
13094         break;
13095       default:
13096         DisplayError(_("No hint available"), 0);
13097         return;
13098     }
13099     SendToProgram("hint\n", &first);
13100     hintRequested = TRUE;
13101 }
13102
13103 void
13104 BookEvent()
13105 {
13106     if (appData.noChessProgram) return;
13107     switch (gameMode) {
13108       case MachinePlaysWhite:
13109         if (WhiteOnMove(forwardMostMove)) {
13110             DisplayError(_("Wait until your turn"), 0);
13111             return;
13112         }
13113         break;
13114       case BeginningOfGame:
13115       case MachinePlaysBlack:
13116         if (!WhiteOnMove(forwardMostMove)) {
13117             DisplayError(_("Wait until your turn"), 0);
13118             return;
13119         }
13120         break;
13121       case EditPosition:
13122         EditPositionDone(TRUE);
13123         break;
13124       case TwoMachinesPlay:
13125         return;
13126       default:
13127         break;
13128     }
13129     SendToProgram("bk\n", &first);
13130     bookOutput[0] = NULLCHAR;
13131     bookRequested = TRUE;
13132 }
13133
13134 void
13135 AboutGameEvent()
13136 {
13137     char *tags = PGNTags(&gameInfo);
13138     TagsPopUp(tags, CmailMsg());
13139     free(tags);
13140 }
13141
13142 /* end button procedures */
13143
13144 void
13145 PrintPosition(fp, move)
13146      FILE *fp;
13147      int move;
13148 {
13149     int i, j;
13150
13151     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13152         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13153             char c = PieceToChar(boards[move][i][j]);
13154             fputc(c == 'x' ? '.' : c, fp);
13155             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13156         }
13157     }
13158     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13159       fprintf(fp, "white to play\n");
13160     else
13161       fprintf(fp, "black to play\n");
13162 }
13163
13164 void
13165 PrintOpponents(fp)
13166      FILE *fp;
13167 {
13168     if (gameInfo.white != NULL) {
13169         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13170     } else {
13171         fprintf(fp, "\n");
13172     }
13173 }
13174
13175 /* Find last component of program's own name, using some heuristics */
13176 void
13177 TidyProgramName(prog, host, buf)
13178      char *prog, *host, buf[MSG_SIZ];
13179 {
13180     char *p, *q;
13181     int local = (strcmp(host, "localhost") == 0);
13182     while (!local && (p = strchr(prog, ';')) != NULL) {
13183         p++;
13184         while (*p == ' ') p++;
13185         prog = p;
13186     }
13187     if (*prog == '"' || *prog == '\'') {
13188         q = strchr(prog + 1, *prog);
13189     } else {
13190         q = strchr(prog, ' ');
13191     }
13192     if (q == NULL) q = prog + strlen(prog);
13193     p = q;
13194     while (p >= prog && *p != '/' && *p != '\\') p--;
13195     p++;
13196     if(p == prog && *p == '"') p++;
13197     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13198     memcpy(buf, p, q - p);
13199     buf[q - p] = NULLCHAR;
13200     if (!local) {
13201         strcat(buf, "@");
13202         strcat(buf, host);
13203     }
13204 }
13205
13206 char *
13207 TimeControlTagValue()
13208 {
13209     char buf[MSG_SIZ];
13210     if (!appData.clockMode) {
13211       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13212     } else if (movesPerSession > 0) {
13213       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13214     } else if (timeIncrement == 0) {
13215       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13216     } else {
13217       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13218     }
13219     return StrSave(buf);
13220 }
13221
13222 void
13223 SetGameInfo()
13224 {
13225     /* This routine is used only for certain modes */
13226     VariantClass v = gameInfo.variant;
13227     ChessMove r = GameUnfinished;
13228     char *p = NULL;
13229
13230     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13231         r = gameInfo.result;
13232         p = gameInfo.resultDetails;
13233         gameInfo.resultDetails = NULL;
13234     }
13235     ClearGameInfo(&gameInfo);
13236     gameInfo.variant = v;
13237
13238     switch (gameMode) {
13239       case MachinePlaysWhite:
13240         gameInfo.event = StrSave( appData.pgnEventHeader );
13241         gameInfo.site = StrSave(HostName());
13242         gameInfo.date = PGNDate();
13243         gameInfo.round = StrSave("-");
13244         gameInfo.white = StrSave(first.tidy);
13245         gameInfo.black = StrSave(UserName());
13246         gameInfo.timeControl = TimeControlTagValue();
13247         break;
13248
13249       case MachinePlaysBlack:
13250         gameInfo.event = StrSave( appData.pgnEventHeader );
13251         gameInfo.site = StrSave(HostName());
13252         gameInfo.date = PGNDate();
13253         gameInfo.round = StrSave("-");
13254         gameInfo.white = StrSave(UserName());
13255         gameInfo.black = StrSave(first.tidy);
13256         gameInfo.timeControl = TimeControlTagValue();
13257         break;
13258
13259       case TwoMachinesPlay:
13260         gameInfo.event = StrSave( appData.pgnEventHeader );
13261         gameInfo.site = StrSave(HostName());
13262         gameInfo.date = PGNDate();
13263         if (matchGame > 0) {
13264             char buf[MSG_SIZ];
13265             snprintf(buf, MSG_SIZ, "%d", matchGame);
13266             gameInfo.round = StrSave(buf);
13267         } else {
13268             gameInfo.round = StrSave("-");
13269         }
13270         if (first.twoMachinesColor[0] == 'w') {
13271             gameInfo.white = StrSave(first.tidy);
13272             gameInfo.black = StrSave(second.tidy);
13273         } else {
13274             gameInfo.white = StrSave(second.tidy);
13275             gameInfo.black = StrSave(first.tidy);
13276         }
13277         gameInfo.timeControl = TimeControlTagValue();
13278         break;
13279
13280       case EditGame:
13281         gameInfo.event = StrSave("Edited game");
13282         gameInfo.site = StrSave(HostName());
13283         gameInfo.date = PGNDate();
13284         gameInfo.round = StrSave("-");
13285         gameInfo.white = StrSave("-");
13286         gameInfo.black = StrSave("-");
13287         gameInfo.result = r;
13288         gameInfo.resultDetails = p;
13289         break;
13290
13291       case EditPosition:
13292         gameInfo.event = StrSave("Edited position");
13293         gameInfo.site = StrSave(HostName());
13294         gameInfo.date = PGNDate();
13295         gameInfo.round = StrSave("-");
13296         gameInfo.white = StrSave("-");
13297         gameInfo.black = StrSave("-");
13298         break;
13299
13300       case IcsPlayingWhite:
13301       case IcsPlayingBlack:
13302       case IcsObserving:
13303       case IcsExamining:
13304         break;
13305
13306       case PlayFromGameFile:
13307         gameInfo.event = StrSave("Game from non-PGN file");
13308         gameInfo.site = StrSave(HostName());
13309         gameInfo.date = PGNDate();
13310         gameInfo.round = StrSave("-");
13311         gameInfo.white = StrSave("?");
13312         gameInfo.black = StrSave("?");
13313         break;
13314
13315       default:
13316         break;
13317     }
13318 }
13319
13320 void
13321 ReplaceComment(index, text)
13322      int index;
13323      char *text;
13324 {
13325     int len;
13326     char *p;
13327     float score;
13328
13329     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13330        pvInfoList[index-1].depth == len &&
13331        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13332        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13333     while (*text == '\n') text++;
13334     len = strlen(text);
13335     while (len > 0 && text[len - 1] == '\n') len--;
13336
13337     if (commentList[index] != NULL)
13338       free(commentList[index]);
13339
13340     if (len == 0) {
13341         commentList[index] = NULL;
13342         return;
13343     }
13344   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13345       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13346       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13347     commentList[index] = (char *) malloc(len + 2);
13348     strncpy(commentList[index], text, len);
13349     commentList[index][len] = '\n';
13350     commentList[index][len + 1] = NULLCHAR;
13351   } else {
13352     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13353     char *p;
13354     commentList[index] = (char *) malloc(len + 7);
13355     safeStrCpy(commentList[index], "{\n", 3);
13356     safeStrCpy(commentList[index]+2, text, len+1);
13357     commentList[index][len+2] = NULLCHAR;
13358     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13359     strcat(commentList[index], "\n}\n");
13360   }
13361 }
13362
13363 void
13364 CrushCRs(text)
13365      char *text;
13366 {
13367   char *p = text;
13368   char *q = text;
13369   char ch;
13370
13371   do {
13372     ch = *p++;
13373     if (ch == '\r') continue;
13374     *q++ = ch;
13375   } while (ch != '\0');
13376 }
13377
13378 void
13379 AppendComment(index, text, addBraces)
13380      int index;
13381      char *text;
13382      Boolean addBraces; // [HGM] braces: tells if we should add {}
13383 {
13384     int oldlen, len;
13385     char *old;
13386
13387 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13388     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13389
13390     CrushCRs(text);
13391     while (*text == '\n') text++;
13392     len = strlen(text);
13393     while (len > 0 && text[len - 1] == '\n') len--;
13394
13395     if (len == 0) return;
13396
13397     if (commentList[index] != NULL) {
13398         old = commentList[index];
13399         oldlen = strlen(old);
13400         while(commentList[index][oldlen-1] ==  '\n')
13401           commentList[index][--oldlen] = NULLCHAR;
13402         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13403         safeStrCpy(commentList[index], old, oldlen + len + 6);
13404         free(old);
13405         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13406         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13407           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13408           while (*text == '\n') { text++; len--; }
13409           commentList[index][--oldlen] = NULLCHAR;
13410       }
13411         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13412         else          strcat(commentList[index], "\n");
13413         strcat(commentList[index], text);
13414         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13415         else          strcat(commentList[index], "\n");
13416     } else {
13417         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13418         if(addBraces)
13419           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13420         else commentList[index][0] = NULLCHAR;
13421         strcat(commentList[index], text);
13422         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13423         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13424     }
13425 }
13426
13427 static char * FindStr( char * text, char * sub_text )
13428 {
13429     char * result = strstr( text, sub_text );
13430
13431     if( result != NULL ) {
13432         result += strlen( sub_text );
13433     }
13434
13435     return result;
13436 }
13437
13438 /* [AS] Try to extract PV info from PGN comment */
13439 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13440 char *GetInfoFromComment( int index, char * text )
13441 {
13442     char * sep = text, *p;
13443
13444     if( text != NULL && index > 0 ) {
13445         int score = 0;
13446         int depth = 0;
13447         int time = -1, sec = 0, deci;
13448         char * s_eval = FindStr( text, "[%eval " );
13449         char * s_emt = FindStr( text, "[%emt " );
13450
13451         if( s_eval != NULL || s_emt != NULL ) {
13452             /* New style */
13453             char delim;
13454
13455             if( s_eval != NULL ) {
13456                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13457                     return text;
13458                 }
13459
13460                 if( delim != ']' ) {
13461                     return text;
13462                 }
13463             }
13464
13465             if( s_emt != NULL ) {
13466             }
13467                 return text;
13468         }
13469         else {
13470             /* We expect something like: [+|-]nnn.nn/dd */
13471             int score_lo = 0;
13472
13473             if(*text != '{') return text; // [HGM] braces: must be normal comment
13474
13475             sep = strchr( text, '/' );
13476             if( sep == NULL || sep < (text+4) ) {
13477                 return text;
13478             }
13479
13480             p = text;
13481             if(p[1] == '(') { // comment starts with PV
13482                p = strchr(p, ')'); // locate end of PV
13483                if(p == NULL || sep < p+5) return text;
13484                // at this point we have something like "{(.*) +0.23/6 ..."
13485                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13486                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13487                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13488             }
13489             time = -1; sec = -1; deci = -1;
13490             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13491                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13492                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13493                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13494                 return text;
13495             }
13496
13497             if( score_lo < 0 || score_lo >= 100 ) {
13498                 return text;
13499             }
13500
13501             if(sec >= 0) time = 600*time + 10*sec; else
13502             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13503
13504             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13505
13506             /* [HGM] PV time: now locate end of PV info */
13507             while( *++sep >= '0' && *sep <= '9'); // strip depth
13508             if(time >= 0)
13509             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13510             if(sec >= 0)
13511             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13512             if(deci >= 0)
13513             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13514             while(*sep == ' ') sep++;
13515         }
13516
13517         if( depth <= 0 ) {
13518             return text;
13519         }
13520
13521         if( time < 0 ) {
13522             time = -1;
13523         }
13524
13525         pvInfoList[index-1].depth = depth;
13526         pvInfoList[index-1].score = score;
13527         pvInfoList[index-1].time  = 10*time; // centi-sec
13528         if(*sep == '}') *sep = 0; else *--sep = '{';
13529         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13530     }
13531     return sep;
13532 }
13533
13534 void
13535 SendToProgram(message, cps)
13536      char *message;
13537      ChessProgramState *cps;
13538 {
13539     int count, outCount, error;
13540     char buf[MSG_SIZ];
13541
13542     if (cps->pr == NULL) return;
13543     Attention(cps);
13544
13545     if (appData.debugMode) {
13546         TimeMark now;
13547         GetTimeMark(&now);
13548         fprintf(debugFP, "%ld >%-6s: %s",
13549                 SubtractTimeMarks(&now, &programStartTime),
13550                 cps->which, message);
13551     }
13552
13553     count = strlen(message);
13554     outCount = OutputToProcess(cps->pr, message, count, &error);
13555     if (outCount < count && !exiting
13556                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13557       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13558         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13559             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13560                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13561                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13562             } else {
13563                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13564             }
13565             gameInfo.resultDetails = StrSave(buf);
13566         }
13567         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13568     }
13569 }
13570
13571 void
13572 ReceiveFromProgram(isr, closure, message, count, error)
13573      InputSourceRef isr;
13574      VOIDSTAR closure;
13575      char *message;
13576      int count;
13577      int error;
13578 {
13579     char *end_str;
13580     char buf[MSG_SIZ];
13581     ChessProgramState *cps = (ChessProgramState *)closure;
13582
13583     if (isr != cps->isr) return; /* Killed intentionally */
13584     if (count <= 0) {
13585         if (count == 0) {
13586             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13587                     cps->which, cps->program);
13588         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13589                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13590                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13591                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13592                 } else {
13593                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13594                 }
13595                 gameInfo.resultDetails = StrSave(buf);
13596             }
13597             RemoveInputSource(cps->isr);
13598             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13599         } else {
13600             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13601                     cps->which, cps->program);
13602             RemoveInputSource(cps->isr);
13603
13604             /* [AS] Program is misbehaving badly... kill it */
13605             if( count == -2 ) {
13606                 DestroyChildProcess( cps->pr, 9 );
13607                 cps->pr = NoProc;
13608             }
13609
13610             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13611         }
13612         return;
13613     }
13614
13615     if ((end_str = strchr(message, '\r')) != NULL)
13616       *end_str = NULLCHAR;
13617     if ((end_str = strchr(message, '\n')) != NULL)
13618       *end_str = NULLCHAR;
13619
13620     if (appData.debugMode) {
13621         TimeMark now; int print = 1;
13622         char *quote = ""; char c; int i;
13623
13624         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13625                 char start = message[0];
13626                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13627                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13628                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13629                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13630                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13631                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13632                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13633                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13634                    sscanf(message, "hint: %c", &c)!=1 && 
13635                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13636                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13637                     print = (appData.engineComments >= 2);
13638                 }
13639                 message[0] = start; // restore original message
13640         }
13641         if(print) {
13642                 GetTimeMark(&now);
13643                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13644                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13645                         quote,
13646                         message);
13647         }
13648     }
13649
13650     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13651     if (appData.icsEngineAnalyze) {
13652         if (strstr(message, "whisper") != NULL ||
13653              strstr(message, "kibitz") != NULL ||
13654             strstr(message, "tellics") != NULL) return;
13655     }
13656
13657     HandleMachineMove(message, cps);
13658 }
13659
13660
13661 void
13662 SendTimeControl(cps, mps, tc, inc, sd, st)
13663      ChessProgramState *cps;
13664      int mps, inc, sd, st;
13665      long tc;
13666 {
13667     char buf[MSG_SIZ];
13668     int seconds;
13669
13670     if( timeControl_2 > 0 ) {
13671         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13672             tc = timeControl_2;
13673         }
13674     }
13675     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13676     inc /= cps->timeOdds;
13677     st  /= cps->timeOdds;
13678
13679     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13680
13681     if (st > 0) {
13682       /* Set exact time per move, normally using st command */
13683       if (cps->stKludge) {
13684         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13685         seconds = st % 60;
13686         if (seconds == 0) {
13687           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13688         } else {
13689           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13690         }
13691       } else {
13692         snprintf(buf, MSG_SIZ, "st %d\n", st);
13693       }
13694     } else {
13695       /* Set conventional or incremental time control, using level command */
13696       if (seconds == 0) {
13697         /* Note old gnuchess bug -- minutes:seconds used to not work.
13698            Fixed in later versions, but still avoid :seconds
13699            when seconds is 0. */
13700         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13701       } else {
13702         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13703                  seconds, inc/1000.);
13704       }
13705     }
13706     SendToProgram(buf, cps);
13707
13708     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13709     /* Orthogonally, limit search to given depth */
13710     if (sd > 0) {
13711       if (cps->sdKludge) {
13712         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13713       } else {
13714         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13715       }
13716       SendToProgram(buf, cps);
13717     }
13718
13719     if(cps->nps > 0) { /* [HGM] nps */
13720         if(cps->supportsNPS == FALSE)
13721           cps->nps = -1; // don't use if engine explicitly says not supported!
13722         else {
13723           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13724           SendToProgram(buf, cps);
13725         }
13726     }
13727 }
13728
13729 ChessProgramState *WhitePlayer()
13730 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13731 {
13732     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13733        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13734         return &second;
13735     return &first;
13736 }
13737
13738 void
13739 SendTimeRemaining(cps, machineWhite)
13740      ChessProgramState *cps;
13741      int /*boolean*/ machineWhite;
13742 {
13743     char message[MSG_SIZ];
13744     long time, otime;
13745
13746     /* Note: this routine must be called when the clocks are stopped
13747        or when they have *just* been set or switched; otherwise
13748        it will be off by the time since the current tick started.
13749     */
13750     if (machineWhite) {
13751         time = whiteTimeRemaining / 10;
13752         otime = blackTimeRemaining / 10;
13753     } else {
13754         time = blackTimeRemaining / 10;
13755         otime = whiteTimeRemaining / 10;
13756     }
13757     /* [HGM] translate opponent's time by time-odds factor */
13758     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13759     if (appData.debugMode) {
13760         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13761     }
13762
13763     if (time <= 0) time = 1;
13764     if (otime <= 0) otime = 1;
13765
13766     snprintf(message, MSG_SIZ, "time %ld\n", time);
13767     SendToProgram(message, cps);
13768
13769     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13770     SendToProgram(message, cps);
13771 }
13772
13773 int
13774 BoolFeature(p, name, loc, cps)
13775      char **p;
13776      char *name;
13777      int *loc;
13778      ChessProgramState *cps;
13779 {
13780   char buf[MSG_SIZ];
13781   int len = strlen(name);
13782   int val;
13783
13784   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13785     (*p) += len + 1;
13786     sscanf(*p, "%d", &val);
13787     *loc = (val != 0);
13788     while (**p && **p != ' ')
13789       (*p)++;
13790     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13791     SendToProgram(buf, cps);
13792     return TRUE;
13793   }
13794   return FALSE;
13795 }
13796
13797 int
13798 IntFeature(p, name, loc, cps)
13799      char **p;
13800      char *name;
13801      int *loc;
13802      ChessProgramState *cps;
13803 {
13804   char buf[MSG_SIZ];
13805   int len = strlen(name);
13806   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13807     (*p) += len + 1;
13808     sscanf(*p, "%d", loc);
13809     while (**p && **p != ' ') (*p)++;
13810     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13811     SendToProgram(buf, cps);
13812     return TRUE;
13813   }
13814   return FALSE;
13815 }
13816
13817 int
13818 StringFeature(p, name, loc, cps)
13819      char **p;
13820      char *name;
13821      char loc[];
13822      ChessProgramState *cps;
13823 {
13824   char buf[MSG_SIZ];
13825   int len = strlen(name);
13826   if (strncmp((*p), name, len) == 0
13827       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13828     (*p) += len + 2;
13829     sscanf(*p, "%[^\"]", loc);
13830     while (**p && **p != '\"') (*p)++;
13831     if (**p == '\"') (*p)++;
13832     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13833     SendToProgram(buf, cps);
13834     return TRUE;
13835   }
13836   return FALSE;
13837 }
13838
13839 int
13840 ParseOption(Option *opt, ChessProgramState *cps)
13841 // [HGM] options: process the string that defines an engine option, and determine
13842 // name, type, default value, and allowed value range
13843 {
13844         char *p, *q, buf[MSG_SIZ];
13845         int n, min = (-1)<<31, max = 1<<31, def;
13846
13847         if(p = strstr(opt->name, " -spin ")) {
13848             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13849             if(max < min) max = min; // enforce consistency
13850             if(def < min) def = min;
13851             if(def > max) def = max;
13852             opt->value = def;
13853             opt->min = min;
13854             opt->max = max;
13855             opt->type = Spin;
13856         } else if((p = strstr(opt->name, " -slider "))) {
13857             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13858             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13859             if(max < min) max = min; // enforce consistency
13860             if(def < min) def = min;
13861             if(def > max) def = max;
13862             opt->value = def;
13863             opt->min = min;
13864             opt->max = max;
13865             opt->type = Spin; // Slider;
13866         } else if((p = strstr(opt->name, " -string "))) {
13867             opt->textValue = p+9;
13868             opt->type = TextBox;
13869         } else if((p = strstr(opt->name, " -file "))) {
13870             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13871             opt->textValue = p+7;
13872             opt->type = TextBox; // FileName;
13873         } else if((p = strstr(opt->name, " -path "))) {
13874             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13875             opt->textValue = p+7;
13876             opt->type = TextBox; // PathName;
13877         } else if(p = strstr(opt->name, " -check ")) {
13878             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13879             opt->value = (def != 0);
13880             opt->type = CheckBox;
13881         } else if(p = strstr(opt->name, " -combo ")) {
13882             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13883             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13884             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13885             opt->value = n = 0;
13886             while(q = StrStr(q, " /// ")) {
13887                 n++; *q = 0;    // count choices, and null-terminate each of them
13888                 q += 5;
13889                 if(*q == '*') { // remember default, which is marked with * prefix
13890                     q++;
13891                     opt->value = n;
13892                 }
13893                 cps->comboList[cps->comboCnt++] = q;
13894             }
13895             cps->comboList[cps->comboCnt++] = NULL;
13896             opt->max = n + 1;
13897             opt->type = ComboBox;
13898         } else if(p = strstr(opt->name, " -button")) {
13899             opt->type = Button;
13900         } else if(p = strstr(opt->name, " -save")) {
13901             opt->type = SaveButton;
13902         } else return FALSE;
13903         *p = 0; // terminate option name
13904         // now look if the command-line options define a setting for this engine option.
13905         if(cps->optionSettings && cps->optionSettings[0])
13906             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13907         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13908           snprintf(buf, MSG_SIZ, "option %s", p);
13909                 if(p = strstr(buf, ",")) *p = 0;
13910                 if(q = strchr(buf, '=')) switch(opt->type) {
13911                     case ComboBox:
13912                         for(n=0; n<opt->max; n++)
13913                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13914                         break;
13915                     case TextBox:
13916                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13917                         break;
13918                     case Spin:
13919                     case CheckBox:
13920                         opt->value = atoi(q+1);
13921                     default:
13922                         break;
13923                 }
13924                 strcat(buf, "\n");
13925                 SendToProgram(buf, cps);
13926         }
13927         return TRUE;
13928 }
13929
13930 void
13931 FeatureDone(cps, val)
13932      ChessProgramState* cps;
13933      int val;
13934 {
13935   DelayedEventCallback cb = GetDelayedEvent();
13936   if ((cb == InitBackEnd3 && cps == &first) ||
13937       (cb == SettingsMenuIfReady && cps == &second) ||
13938       (cb == TwoMachinesEventIfReady && cps == &second)) {
13939     CancelDelayedEvent();
13940     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13941   }
13942   cps->initDone = val;
13943 }
13944
13945 /* Parse feature command from engine */
13946 void
13947 ParseFeatures(args, cps)
13948      char* args;
13949      ChessProgramState *cps;
13950 {
13951   char *p = args;
13952   char *q;
13953   int val;
13954   char buf[MSG_SIZ];
13955
13956   for (;;) {
13957     while (*p == ' ') p++;
13958     if (*p == NULLCHAR) return;
13959
13960     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13961     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13962     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13963     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13964     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13965     if (BoolFeature(&p, "reuse", &val, cps)) {
13966       /* Engine can disable reuse, but can't enable it if user said no */
13967       if (!val) cps->reuse = FALSE;
13968       continue;
13969     }
13970     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13971     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13972       if (gameMode == TwoMachinesPlay) {
13973         DisplayTwoMachinesTitle();
13974       } else {
13975         DisplayTitle("");
13976       }
13977       continue;
13978     }
13979     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13980     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13981     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13982     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13983     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13984     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13985     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13986     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13987     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13988     if (IntFeature(&p, "done", &val, cps)) {
13989       FeatureDone(cps, val);
13990       continue;
13991     }
13992     /* Added by Tord: */
13993     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13994     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13995     /* End of additions by Tord */
13996
13997     /* [HGM] added features: */
13998     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13999     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14000     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14001     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14002     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14003     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14004     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14005         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14006           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14007             SendToProgram(buf, cps);
14008             continue;
14009         }
14010         if(cps->nrOptions >= MAX_OPTIONS) {
14011             cps->nrOptions--;
14012             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
14013             DisplayError(buf, 0);
14014         }
14015         continue;
14016     }
14017     /* End of additions by HGM */
14018
14019     /* unknown feature: complain and skip */
14020     q = p;
14021     while (*q && *q != '=') q++;
14022     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14023     SendToProgram(buf, cps);
14024     p = q;
14025     if (*p == '=') {
14026       p++;
14027       if (*p == '\"') {
14028         p++;
14029         while (*p && *p != '\"') p++;
14030         if (*p == '\"') p++;
14031       } else {
14032         while (*p && *p != ' ') p++;
14033       }
14034     }
14035   }
14036
14037 }
14038
14039 void
14040 PeriodicUpdatesEvent(newState)
14041      int newState;
14042 {
14043     if (newState == appData.periodicUpdates)
14044       return;
14045
14046     appData.periodicUpdates=newState;
14047
14048     /* Display type changes, so update it now */
14049 //    DisplayAnalysis();
14050
14051     /* Get the ball rolling again... */
14052     if (newState) {
14053         AnalysisPeriodicEvent(1);
14054         StartAnalysisClock();
14055     }
14056 }
14057
14058 void
14059 PonderNextMoveEvent(newState)
14060      int newState;
14061 {
14062     if (newState == appData.ponderNextMove) return;
14063     if (gameMode == EditPosition) EditPositionDone(TRUE);
14064     if (newState) {
14065         SendToProgram("hard\n", &first);
14066         if (gameMode == TwoMachinesPlay) {
14067             SendToProgram("hard\n", &second);
14068         }
14069     } else {
14070         SendToProgram("easy\n", &first);
14071         thinkOutput[0] = NULLCHAR;
14072         if (gameMode == TwoMachinesPlay) {
14073             SendToProgram("easy\n", &second);
14074         }
14075     }
14076     appData.ponderNextMove = newState;
14077 }
14078
14079 void
14080 NewSettingEvent(option, feature, command, value)
14081      char *command;
14082      int option, value, *feature;
14083 {
14084     char buf[MSG_SIZ];
14085
14086     if (gameMode == EditPosition) EditPositionDone(TRUE);
14087     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14088     if(feature == NULL || *feature) SendToProgram(buf, &first);
14089     if (gameMode == TwoMachinesPlay) {
14090         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14091     }
14092 }
14093
14094 void
14095 ShowThinkingEvent()
14096 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14097 {
14098     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14099     int newState = appData.showThinking
14100         // [HGM] thinking: other features now need thinking output as well
14101         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14102
14103     if (oldState == newState) return;
14104     oldState = newState;
14105     if (gameMode == EditPosition) EditPositionDone(TRUE);
14106     if (oldState) {
14107         SendToProgram("post\n", &first);
14108         if (gameMode == TwoMachinesPlay) {
14109             SendToProgram("post\n", &second);
14110         }
14111     } else {
14112         SendToProgram("nopost\n", &first);
14113         thinkOutput[0] = NULLCHAR;
14114         if (gameMode == TwoMachinesPlay) {
14115             SendToProgram("nopost\n", &second);
14116         }
14117     }
14118 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14119 }
14120
14121 void
14122 AskQuestionEvent(title, question, replyPrefix, which)
14123      char *title; char *question; char *replyPrefix; char *which;
14124 {
14125   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14126   if (pr == NoProc) return;
14127   AskQuestion(title, question, replyPrefix, pr);
14128 }
14129
14130 void
14131 DisplayMove(moveNumber)
14132      int moveNumber;
14133 {
14134     char message[MSG_SIZ];
14135     char res[MSG_SIZ];
14136     char cpThinkOutput[MSG_SIZ];
14137
14138     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14139
14140     if (moveNumber == forwardMostMove - 1 ||
14141         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14142
14143         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14144
14145         if (strchr(cpThinkOutput, '\n')) {
14146             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14147         }
14148     } else {
14149         *cpThinkOutput = NULLCHAR;
14150     }
14151
14152     /* [AS] Hide thinking from human user */
14153     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14154         *cpThinkOutput = NULLCHAR;
14155         if( thinkOutput[0] != NULLCHAR ) {
14156             int i;
14157
14158             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14159                 cpThinkOutput[i] = '.';
14160             }
14161             cpThinkOutput[i] = NULLCHAR;
14162             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14163         }
14164     }
14165
14166     if (moveNumber == forwardMostMove - 1 &&
14167         gameInfo.resultDetails != NULL) {
14168         if (gameInfo.resultDetails[0] == NULLCHAR) {
14169           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14170         } else {
14171           snprintf(res, MSG_SIZ, " {%s} %s",
14172                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14173         }
14174     } else {
14175         res[0] = NULLCHAR;
14176     }
14177
14178     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14179         DisplayMessage(res, cpThinkOutput);
14180     } else {
14181       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14182                 WhiteOnMove(moveNumber) ? " " : ".. ",
14183                 parseList[moveNumber], res);
14184         DisplayMessage(message, cpThinkOutput);
14185     }
14186 }
14187
14188 void
14189 DisplayComment(moveNumber, text)
14190      int moveNumber;
14191      char *text;
14192 {
14193     char title[MSG_SIZ];
14194     char buf[8000]; // comment can be long!
14195     int score, depth;
14196
14197     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14198       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14199     } else {
14200       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14201               WhiteOnMove(moveNumber) ? " " : ".. ",
14202               parseList[moveNumber]);
14203     }
14204     // [HGM] PV info: display PV info together with (or as) comment
14205     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14206       if(text == NULL) text = "";
14207       score = pvInfoList[moveNumber].score;
14208       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14209               depth, (pvInfoList[moveNumber].time+50)/100, text);
14210       text = buf;
14211     }
14212     if (text != NULL && (appData.autoDisplayComment || commentUp))
14213         CommentPopUp(title, text);
14214 }
14215
14216 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14217  * might be busy thinking or pondering.  It can be omitted if your
14218  * gnuchess is configured to stop thinking immediately on any user
14219  * input.  However, that gnuchess feature depends on the FIONREAD
14220  * ioctl, which does not work properly on some flavors of Unix.
14221  */
14222 void
14223 Attention(cps)
14224      ChessProgramState *cps;
14225 {
14226 #if ATTENTION
14227     if (!cps->useSigint) return;
14228     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14229     switch (gameMode) {
14230       case MachinePlaysWhite:
14231       case MachinePlaysBlack:
14232       case TwoMachinesPlay:
14233       case IcsPlayingWhite:
14234       case IcsPlayingBlack:
14235       case AnalyzeMode:
14236       case AnalyzeFile:
14237         /* Skip if we know it isn't thinking */
14238         if (!cps->maybeThinking) return;
14239         if (appData.debugMode)
14240           fprintf(debugFP, "Interrupting %s\n", cps->which);
14241         InterruptChildProcess(cps->pr);
14242         cps->maybeThinking = FALSE;
14243         break;
14244       default:
14245         break;
14246     }
14247 #endif /*ATTENTION*/
14248 }
14249
14250 int
14251 CheckFlags()
14252 {
14253     if (whiteTimeRemaining <= 0) {
14254         if (!whiteFlag) {
14255             whiteFlag = TRUE;
14256             if (appData.icsActive) {
14257                 if (appData.autoCallFlag &&
14258                     gameMode == IcsPlayingBlack && !blackFlag) {
14259                   SendToICS(ics_prefix);
14260                   SendToICS("flag\n");
14261                 }
14262             } else {
14263                 if (blackFlag) {
14264                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14265                 } else {
14266                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14267                     if (appData.autoCallFlag) {
14268                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14269                         return TRUE;
14270                     }
14271                 }
14272             }
14273         }
14274     }
14275     if (blackTimeRemaining <= 0) {
14276         if (!blackFlag) {
14277             blackFlag = TRUE;
14278             if (appData.icsActive) {
14279                 if (appData.autoCallFlag &&
14280                     gameMode == IcsPlayingWhite && !whiteFlag) {
14281                   SendToICS(ics_prefix);
14282                   SendToICS("flag\n");
14283                 }
14284             } else {
14285                 if (whiteFlag) {
14286                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14287                 } else {
14288                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14289                     if (appData.autoCallFlag) {
14290                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14291                         return TRUE;
14292                     }
14293                 }
14294             }
14295         }
14296     }
14297     return FALSE;
14298 }
14299
14300 void
14301 CheckTimeControl()
14302 {
14303     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14304         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14305
14306     /*
14307      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14308      */
14309     if ( !WhiteOnMove(forwardMostMove) ) {
14310         /* White made time control */
14311         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14312         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14313         /* [HGM] time odds: correct new time quota for time odds! */
14314                                             / WhitePlayer()->timeOdds;
14315         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14316     } else {
14317         lastBlack -= blackTimeRemaining;
14318         /* Black made time control */
14319         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14320                                             / WhitePlayer()->other->timeOdds;
14321         lastWhite = whiteTimeRemaining;
14322     }
14323 }
14324
14325 void
14326 DisplayBothClocks()
14327 {
14328     int wom = gameMode == EditPosition ?
14329       !blackPlaysFirst : WhiteOnMove(currentMove);
14330     DisplayWhiteClock(whiteTimeRemaining, wom);
14331     DisplayBlackClock(blackTimeRemaining, !wom);
14332 }
14333
14334
14335 /* Timekeeping seems to be a portability nightmare.  I think everyone
14336    has ftime(), but I'm really not sure, so I'm including some ifdefs
14337    to use other calls if you don't.  Clocks will be less accurate if
14338    you have neither ftime nor gettimeofday.
14339 */
14340
14341 /* VS 2008 requires the #include outside of the function */
14342 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14343 #include <sys/timeb.h>
14344 #endif
14345
14346 /* Get the current time as a TimeMark */
14347 void
14348 GetTimeMark(tm)
14349      TimeMark *tm;
14350 {
14351 #if HAVE_GETTIMEOFDAY
14352
14353     struct timeval timeVal;
14354     struct timezone timeZone;
14355
14356     gettimeofday(&timeVal, &timeZone);
14357     tm->sec = (long) timeVal.tv_sec;
14358     tm->ms = (int) (timeVal.tv_usec / 1000L);
14359
14360 #else /*!HAVE_GETTIMEOFDAY*/
14361 #if HAVE_FTIME
14362
14363 // include <sys/timeb.h> / moved to just above start of function
14364     struct timeb timeB;
14365
14366     ftime(&timeB);
14367     tm->sec = (long) timeB.time;
14368     tm->ms = (int) timeB.millitm;
14369
14370 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14371     tm->sec = (long) time(NULL);
14372     tm->ms = 0;
14373 #endif
14374 #endif
14375 }
14376
14377 /* Return the difference in milliseconds between two
14378    time marks.  We assume the difference will fit in a long!
14379 */
14380 long
14381 SubtractTimeMarks(tm2, tm1)
14382      TimeMark *tm2, *tm1;
14383 {
14384     return 1000L*(tm2->sec - tm1->sec) +
14385            (long) (tm2->ms - tm1->ms);
14386 }
14387
14388
14389 /*
14390  * Code to manage the game clocks.
14391  *
14392  * In tournament play, black starts the clock and then white makes a move.
14393  * We give the human user a slight advantage if he is playing white---the
14394  * clocks don't run until he makes his first move, so it takes zero time.
14395  * Also, we don't account for network lag, so we could get out of sync
14396  * with GNU Chess's clock -- but then, referees are always right.
14397  */
14398
14399 static TimeMark tickStartTM;
14400 static long intendedTickLength;
14401
14402 long
14403 NextTickLength(timeRemaining)
14404      long timeRemaining;
14405 {
14406     long nominalTickLength, nextTickLength;
14407
14408     if (timeRemaining > 0L && timeRemaining <= 10000L)
14409       nominalTickLength = 100L;
14410     else
14411       nominalTickLength = 1000L;
14412     nextTickLength = timeRemaining % nominalTickLength;
14413     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14414
14415     return nextTickLength;
14416 }
14417
14418 /* Adjust clock one minute up or down */
14419 void
14420 AdjustClock(Boolean which, int dir)
14421 {
14422     if(which) blackTimeRemaining += 60000*dir;
14423     else      whiteTimeRemaining += 60000*dir;
14424     DisplayBothClocks();
14425 }
14426
14427 /* Stop clocks and reset to a fresh time control */
14428 void
14429 ResetClocks()
14430 {
14431     (void) StopClockTimer();
14432     if (appData.icsActive) {
14433         whiteTimeRemaining = blackTimeRemaining = 0;
14434     } else if (searchTime) {
14435         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14436         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14437     } else { /* [HGM] correct new time quote for time odds */
14438         whiteTC = blackTC = fullTimeControlString;
14439         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14440         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14441     }
14442     if (whiteFlag || blackFlag) {
14443         DisplayTitle("");
14444         whiteFlag = blackFlag = FALSE;
14445     }
14446     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14447     DisplayBothClocks();
14448 }
14449
14450 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14451
14452 /* Decrement running clock by amount of time that has passed */
14453 void
14454 DecrementClocks()
14455 {
14456     long timeRemaining;
14457     long lastTickLength, fudge;
14458     TimeMark now;
14459
14460     if (!appData.clockMode) return;
14461     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14462
14463     GetTimeMark(&now);
14464
14465     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14466
14467     /* Fudge if we woke up a little too soon */
14468     fudge = intendedTickLength - lastTickLength;
14469     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14470
14471     if (WhiteOnMove(forwardMostMove)) {
14472         if(whiteNPS >= 0) lastTickLength = 0;
14473         timeRemaining = whiteTimeRemaining -= lastTickLength;
14474         if(timeRemaining < 0 && !appData.icsActive) {
14475             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14476             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14477                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14478                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14479             }
14480         }
14481         DisplayWhiteClock(whiteTimeRemaining - fudge,
14482                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14483     } else {
14484         if(blackNPS >= 0) lastTickLength = 0;
14485         timeRemaining = blackTimeRemaining -= lastTickLength;
14486         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14487             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14488             if(suddenDeath) {
14489                 blackStartMove = forwardMostMove;
14490                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14491             }
14492         }
14493         DisplayBlackClock(blackTimeRemaining - fudge,
14494                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14495     }
14496     if (CheckFlags()) return;
14497
14498     tickStartTM = now;
14499     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14500     StartClockTimer(intendedTickLength);
14501
14502     /* if the time remaining has fallen below the alarm threshold, sound the
14503      * alarm. if the alarm has sounded and (due to a takeback or time control
14504      * with increment) the time remaining has increased to a level above the
14505      * threshold, reset the alarm so it can sound again.
14506      */
14507
14508     if (appData.icsActive && appData.icsAlarm) {
14509
14510         /* make sure we are dealing with the user's clock */
14511         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14512                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14513            )) return;
14514
14515         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14516             alarmSounded = FALSE;
14517         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14518             PlayAlarmSound();
14519             alarmSounded = TRUE;
14520         }
14521     }
14522 }
14523
14524
14525 /* A player has just moved, so stop the previously running
14526    clock and (if in clock mode) start the other one.
14527    We redisplay both clocks in case we're in ICS mode, because
14528    ICS gives us an update to both clocks after every move.
14529    Note that this routine is called *after* forwardMostMove
14530    is updated, so the last fractional tick must be subtracted
14531    from the color that is *not* on move now.
14532 */
14533 void
14534 SwitchClocks(int newMoveNr)
14535 {
14536     long lastTickLength;
14537     TimeMark now;
14538     int flagged = FALSE;
14539
14540     GetTimeMark(&now);
14541
14542     if (StopClockTimer() && appData.clockMode) {
14543         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14544         if (!WhiteOnMove(forwardMostMove)) {
14545             if(blackNPS >= 0) lastTickLength = 0;
14546             blackTimeRemaining -= lastTickLength;
14547            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14548 //         if(pvInfoList[forwardMostMove].time == -1)
14549                  pvInfoList[forwardMostMove].time =               // use GUI time
14550                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14551         } else {
14552            if(whiteNPS >= 0) lastTickLength = 0;
14553            whiteTimeRemaining -= lastTickLength;
14554            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14555 //         if(pvInfoList[forwardMostMove].time == -1)
14556                  pvInfoList[forwardMostMove].time =
14557                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14558         }
14559         flagged = CheckFlags();
14560     }
14561     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14562     CheckTimeControl();
14563
14564     if (flagged || !appData.clockMode) return;
14565
14566     switch (gameMode) {
14567       case MachinePlaysBlack:
14568       case MachinePlaysWhite:
14569       case BeginningOfGame:
14570         if (pausing) return;
14571         break;
14572
14573       case EditGame:
14574       case PlayFromGameFile:
14575       case IcsExamining:
14576         return;
14577
14578       default:
14579         break;
14580     }
14581
14582     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14583         if(WhiteOnMove(forwardMostMove))
14584              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14585         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14586     }
14587
14588     tickStartTM = now;
14589     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14590       whiteTimeRemaining : blackTimeRemaining);
14591     StartClockTimer(intendedTickLength);
14592 }
14593
14594
14595 /* Stop both clocks */
14596 void
14597 StopClocks()
14598 {
14599     long lastTickLength;
14600     TimeMark now;
14601
14602     if (!StopClockTimer()) return;
14603     if (!appData.clockMode) return;
14604
14605     GetTimeMark(&now);
14606
14607     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14608     if (WhiteOnMove(forwardMostMove)) {
14609         if(whiteNPS >= 0) lastTickLength = 0;
14610         whiteTimeRemaining -= lastTickLength;
14611         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14612     } else {
14613         if(blackNPS >= 0) lastTickLength = 0;
14614         blackTimeRemaining -= lastTickLength;
14615         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14616     }
14617     CheckFlags();
14618 }
14619
14620 /* Start clock of player on move.  Time may have been reset, so
14621    if clock is already running, stop and restart it. */
14622 void
14623 StartClocks()
14624 {
14625     (void) StopClockTimer(); /* in case it was running already */
14626     DisplayBothClocks();
14627     if (CheckFlags()) return;
14628
14629     if (!appData.clockMode) return;
14630     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14631
14632     GetTimeMark(&tickStartTM);
14633     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14634       whiteTimeRemaining : blackTimeRemaining);
14635
14636    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14637     whiteNPS = blackNPS = -1;
14638     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14639        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14640         whiteNPS = first.nps;
14641     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14642        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14643         blackNPS = first.nps;
14644     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14645         whiteNPS = second.nps;
14646     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14647         blackNPS = second.nps;
14648     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14649
14650     StartClockTimer(intendedTickLength);
14651 }
14652
14653 char *
14654 TimeString(ms)
14655      long ms;
14656 {
14657     long second, minute, hour, day;
14658     char *sign = "";
14659     static char buf[32];
14660
14661     if (ms > 0 && ms <= 9900) {
14662       /* convert milliseconds to tenths, rounding up */
14663       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14664
14665       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14666       return buf;
14667     }
14668
14669     /* convert milliseconds to seconds, rounding up */
14670     /* use floating point to avoid strangeness of integer division
14671        with negative dividends on many machines */
14672     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14673
14674     if (second < 0) {
14675         sign = "-";
14676         second = -second;
14677     }
14678
14679     day = second / (60 * 60 * 24);
14680     second = second % (60 * 60 * 24);
14681     hour = second / (60 * 60);
14682     second = second % (60 * 60);
14683     minute = second / 60;
14684     second = second % 60;
14685
14686     if (day > 0)
14687       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14688               sign, day, hour, minute, second);
14689     else if (hour > 0)
14690       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14691     else
14692       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14693
14694     return buf;
14695 }
14696
14697
14698 /*
14699  * This is necessary because some C libraries aren't ANSI C compliant yet.
14700  */
14701 char *
14702 StrStr(string, match)
14703      char *string, *match;
14704 {
14705     int i, length;
14706
14707     length = strlen(match);
14708
14709     for (i = strlen(string) - length; i >= 0; i--, string++)
14710       if (!strncmp(match, string, length))
14711         return string;
14712
14713     return NULL;
14714 }
14715
14716 char *
14717 StrCaseStr(string, match)
14718      char *string, *match;
14719 {
14720     int i, j, length;
14721
14722     length = strlen(match);
14723
14724     for (i = strlen(string) - length; i >= 0; i--, string++) {
14725         for (j = 0; j < length; j++) {
14726             if (ToLower(match[j]) != ToLower(string[j]))
14727               break;
14728         }
14729         if (j == length) return string;
14730     }
14731
14732     return NULL;
14733 }
14734
14735 #ifndef _amigados
14736 int
14737 StrCaseCmp(s1, s2)
14738      char *s1, *s2;
14739 {
14740     char c1, c2;
14741
14742     for (;;) {
14743         c1 = ToLower(*s1++);
14744         c2 = ToLower(*s2++);
14745         if (c1 > c2) return 1;
14746         if (c1 < c2) return -1;
14747         if (c1 == NULLCHAR) return 0;
14748     }
14749 }
14750
14751
14752 int
14753 ToLower(c)
14754      int c;
14755 {
14756     return isupper(c) ? tolower(c) : c;
14757 }
14758
14759
14760 int
14761 ToUpper(c)
14762      int c;
14763 {
14764     return islower(c) ? toupper(c) : c;
14765 }
14766 #endif /* !_amigados    */
14767
14768 char *
14769 StrSave(s)
14770      char *s;
14771 {
14772   char *ret;
14773
14774   if ((ret = (char *) malloc(strlen(s) + 1)))
14775     {
14776       safeStrCpy(ret, s, strlen(s)+1);
14777     }
14778   return ret;
14779 }
14780
14781 char *
14782 StrSavePtr(s, savePtr)
14783      char *s, **savePtr;
14784 {
14785     if (*savePtr) {
14786         free(*savePtr);
14787     }
14788     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14789       safeStrCpy(*savePtr, s, strlen(s)+1);
14790     }
14791     return(*savePtr);
14792 }
14793
14794 char *
14795 PGNDate()
14796 {
14797     time_t clock;
14798     struct tm *tm;
14799     char buf[MSG_SIZ];
14800
14801     clock = time((time_t *)NULL);
14802     tm = localtime(&clock);
14803     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14804             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14805     return StrSave(buf);
14806 }
14807
14808
14809 char *
14810 PositionToFEN(move, overrideCastling)
14811      int move;
14812      char *overrideCastling;
14813 {
14814     int i, j, fromX, fromY, toX, toY;
14815     int whiteToPlay;
14816     char buf[128];
14817     char *p, *q;
14818     int emptycount;
14819     ChessSquare piece;
14820
14821     whiteToPlay = (gameMode == EditPosition) ?
14822       !blackPlaysFirst : (move % 2 == 0);
14823     p = buf;
14824
14825     /* Piece placement data */
14826     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14827         emptycount = 0;
14828         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14829             if (boards[move][i][j] == EmptySquare) {
14830                 emptycount++;
14831             } else { ChessSquare piece = boards[move][i][j];
14832                 if (emptycount > 0) {
14833                     if(emptycount<10) /* [HGM] can be >= 10 */
14834                         *p++ = '0' + emptycount;
14835                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14836                     emptycount = 0;
14837                 }
14838                 if(PieceToChar(piece) == '+') {
14839                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14840                     *p++ = '+';
14841                     piece = (ChessSquare)(DEMOTED piece);
14842                 }
14843                 *p++ = PieceToChar(piece);
14844                 if(p[-1] == '~') {
14845                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14846                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14847                     *p++ = '~';
14848                 }
14849             }
14850         }
14851         if (emptycount > 0) {
14852             if(emptycount<10) /* [HGM] can be >= 10 */
14853                 *p++ = '0' + emptycount;
14854             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14855             emptycount = 0;
14856         }
14857         *p++ = '/';
14858     }
14859     *(p - 1) = ' ';
14860
14861     /* [HGM] print Crazyhouse or Shogi holdings */
14862     if( gameInfo.holdingsWidth ) {
14863         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14864         q = p;
14865         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14866             piece = boards[move][i][BOARD_WIDTH-1];
14867             if( piece != EmptySquare )
14868               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14869                   *p++ = PieceToChar(piece);
14870         }
14871         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14872             piece = boards[move][BOARD_HEIGHT-i-1][0];
14873             if( piece != EmptySquare )
14874               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14875                   *p++ = PieceToChar(piece);
14876         }
14877
14878         if( q == p ) *p++ = '-';
14879         *p++ = ']';
14880         *p++ = ' ';
14881     }
14882
14883     /* Active color */
14884     *p++ = whiteToPlay ? 'w' : 'b';
14885     *p++ = ' ';
14886
14887   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14888     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14889   } else {
14890   if(nrCastlingRights) {
14891      q = p;
14892      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14893        /* [HGM] write directly from rights */
14894            if(boards[move][CASTLING][2] != NoRights &&
14895               boards[move][CASTLING][0] != NoRights   )
14896                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14897            if(boards[move][CASTLING][2] != NoRights &&
14898               boards[move][CASTLING][1] != NoRights   )
14899                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14900            if(boards[move][CASTLING][5] != NoRights &&
14901               boards[move][CASTLING][3] != NoRights   )
14902                 *p++ = boards[move][CASTLING][3] + AAA;
14903            if(boards[move][CASTLING][5] != NoRights &&
14904               boards[move][CASTLING][4] != NoRights   )
14905                 *p++ = boards[move][CASTLING][4] + AAA;
14906      } else {
14907
14908         /* [HGM] write true castling rights */
14909         if( nrCastlingRights == 6 ) {
14910             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14911                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14912             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14913                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14914             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14915                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14916             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14917                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14918         }
14919      }
14920      if (q == p) *p++ = '-'; /* No castling rights */
14921      *p++ = ' ';
14922   }
14923
14924   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14925      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14926     /* En passant target square */
14927     if (move > backwardMostMove) {
14928         fromX = moveList[move - 1][0] - AAA;
14929         fromY = moveList[move - 1][1] - ONE;
14930         toX = moveList[move - 1][2] - AAA;
14931         toY = moveList[move - 1][3] - ONE;
14932         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14933             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14934             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14935             fromX == toX) {
14936             /* 2-square pawn move just happened */
14937             *p++ = toX + AAA;
14938             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14939         } else {
14940             *p++ = '-';
14941         }
14942     } else if(move == backwardMostMove) {
14943         // [HGM] perhaps we should always do it like this, and forget the above?
14944         if((signed char)boards[move][EP_STATUS] >= 0) {
14945             *p++ = boards[move][EP_STATUS] + AAA;
14946             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14947         } else {
14948             *p++ = '-';
14949         }
14950     } else {
14951         *p++ = '-';
14952     }
14953     *p++ = ' ';
14954   }
14955   }
14956
14957     /* [HGM] find reversible plies */
14958     {   int i = 0, j=move;
14959
14960         if (appData.debugMode) { int k;
14961             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14962             for(k=backwardMostMove; k<=forwardMostMove; k++)
14963                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14964
14965         }
14966
14967         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14968         if( j == backwardMostMove ) i += initialRulePlies;
14969         sprintf(p, "%d ", i);
14970         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14971     }
14972     /* Fullmove number */
14973     sprintf(p, "%d", (move / 2) + 1);
14974
14975     return StrSave(buf);
14976 }
14977
14978 Boolean
14979 ParseFEN(board, blackPlaysFirst, fen)
14980     Board board;
14981      int *blackPlaysFirst;
14982      char *fen;
14983 {
14984     int i, j;
14985     char *p, c;
14986     int emptycount;
14987     ChessSquare piece;
14988
14989     p = fen;
14990
14991     /* [HGM] by default clear Crazyhouse holdings, if present */
14992     if(gameInfo.holdingsWidth) {
14993        for(i=0; i<BOARD_HEIGHT; i++) {
14994            board[i][0]             = EmptySquare; /* black holdings */
14995            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14996            board[i][1]             = (ChessSquare) 0; /* black counts */
14997            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14998        }
14999     }
15000
15001     /* Piece placement data */
15002     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15003         j = 0;
15004         for (;;) {
15005             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15006                 if (*p == '/') p++;
15007                 emptycount = gameInfo.boardWidth - j;
15008                 while (emptycount--)
15009                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15010                 break;
15011 #if(BOARD_FILES >= 10)
15012             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15013                 p++; emptycount=10;
15014                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15015                 while (emptycount--)
15016                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15017 #endif
15018             } else if (isdigit(*p)) {
15019                 emptycount = *p++ - '0';
15020                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15021                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15022                 while (emptycount--)
15023                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15024             } else if (*p == '+' || isalpha(*p)) {
15025                 if (j >= gameInfo.boardWidth) return FALSE;
15026                 if(*p=='+') {
15027                     piece = CharToPiece(*++p);
15028                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15029                     piece = (ChessSquare) (PROMOTED piece ); p++;
15030                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15031                 } else piece = CharToPiece(*p++);
15032
15033                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15034                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15035                     piece = (ChessSquare) (PROMOTED piece);
15036                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15037                     p++;
15038                 }
15039                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15040             } else {
15041                 return FALSE;
15042             }
15043         }
15044     }
15045     while (*p == '/' || *p == ' ') p++;
15046
15047     /* [HGM] look for Crazyhouse holdings here */
15048     while(*p==' ') p++;
15049     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15050         if(*p == '[') p++;
15051         if(*p == '-' ) p++; /* empty holdings */ else {
15052             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15053             /* if we would allow FEN reading to set board size, we would   */
15054             /* have to add holdings and shift the board read so far here   */
15055             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15056                 p++;
15057                 if((int) piece >= (int) BlackPawn ) {
15058                     i = (int)piece - (int)BlackPawn;
15059                     i = PieceToNumber((ChessSquare)i);
15060                     if( i >= gameInfo.holdingsSize ) return FALSE;
15061                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15062                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15063                 } else {
15064                     i = (int)piece - (int)WhitePawn;
15065                     i = PieceToNumber((ChessSquare)i);
15066                     if( i >= gameInfo.holdingsSize ) return FALSE;
15067                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15068                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15069                 }
15070             }
15071         }
15072         if(*p == ']') p++;
15073     }
15074
15075     while(*p == ' ') p++;
15076
15077     /* Active color */
15078     c = *p++;
15079     if(appData.colorNickNames) {
15080       if( c == appData.colorNickNames[0] ) c = 'w'; else
15081       if( c == appData.colorNickNames[1] ) c = 'b';
15082     }
15083     switch (c) {
15084       case 'w':
15085         *blackPlaysFirst = FALSE;
15086         break;
15087       case 'b':
15088         *blackPlaysFirst = TRUE;
15089         break;
15090       default:
15091         return FALSE;
15092     }
15093
15094     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15095     /* return the extra info in global variiables             */
15096
15097     /* set defaults in case FEN is incomplete */
15098     board[EP_STATUS] = EP_UNKNOWN;
15099     for(i=0; i<nrCastlingRights; i++ ) {
15100         board[CASTLING][i] =
15101             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15102     }   /* assume possible unless obviously impossible */
15103     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15104     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15105     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15106                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15107     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15108     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15109     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15110                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15111     FENrulePlies = 0;
15112
15113     while(*p==' ') p++;
15114     if(nrCastlingRights) {
15115       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15116           /* castling indicator present, so default becomes no castlings */
15117           for(i=0; i<nrCastlingRights; i++ ) {
15118                  board[CASTLING][i] = NoRights;
15119           }
15120       }
15121       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15122              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15123              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15124              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15125         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15126
15127         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15128             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15129             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15130         }
15131         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15132             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15133         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15134                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15135         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15136                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15137         switch(c) {
15138           case'K':
15139               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15140               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15141               board[CASTLING][2] = whiteKingFile;
15142               break;
15143           case'Q':
15144               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15145               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15146               board[CASTLING][2] = whiteKingFile;
15147               break;
15148           case'k':
15149               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15150               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15151               board[CASTLING][5] = blackKingFile;
15152               break;
15153           case'q':
15154               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15155               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15156               board[CASTLING][5] = blackKingFile;
15157           case '-':
15158               break;
15159           default: /* FRC castlings */
15160               if(c >= 'a') { /* black rights */
15161                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15162                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15163                   if(i == BOARD_RGHT) break;
15164                   board[CASTLING][5] = i;
15165                   c -= AAA;
15166                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15167                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15168                   if(c > i)
15169                       board[CASTLING][3] = c;
15170                   else
15171                       board[CASTLING][4] = c;
15172               } else { /* white rights */
15173                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15174                     if(board[0][i] == WhiteKing) break;
15175                   if(i == BOARD_RGHT) break;
15176                   board[CASTLING][2] = i;
15177                   c -= AAA - 'a' + 'A';
15178                   if(board[0][c] >= WhiteKing) break;
15179                   if(c > i)
15180                       board[CASTLING][0] = c;
15181                   else
15182                       board[CASTLING][1] = c;
15183               }
15184         }
15185       }
15186       for(i=0; i<nrCastlingRights; i++)
15187         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15188     if (appData.debugMode) {
15189         fprintf(debugFP, "FEN castling rights:");
15190         for(i=0; i<nrCastlingRights; i++)
15191         fprintf(debugFP, " %d", board[CASTLING][i]);
15192         fprintf(debugFP, "\n");
15193     }
15194
15195       while(*p==' ') p++;
15196     }
15197
15198     /* read e.p. field in games that know e.p. capture */
15199     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15200        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15201       if(*p=='-') {
15202         p++; board[EP_STATUS] = EP_NONE;
15203       } else {
15204          char c = *p++ - AAA;
15205
15206          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15207          if(*p >= '0' && *p <='9') p++;
15208          board[EP_STATUS] = c;
15209       }
15210     }
15211
15212
15213     if(sscanf(p, "%d", &i) == 1) {
15214         FENrulePlies = i; /* 50-move ply counter */
15215         /* (The move number is still ignored)    */
15216     }
15217
15218     return TRUE;
15219 }
15220
15221 void
15222 EditPositionPasteFEN(char *fen)
15223 {
15224   if (fen != NULL) {
15225     Board initial_position;
15226
15227     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15228       DisplayError(_("Bad FEN position in clipboard"), 0);
15229       return ;
15230     } else {
15231       int savedBlackPlaysFirst = blackPlaysFirst;
15232       EditPositionEvent();
15233       blackPlaysFirst = savedBlackPlaysFirst;
15234       CopyBoard(boards[0], initial_position);
15235       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15236       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15237       DisplayBothClocks();
15238       DrawPosition(FALSE, boards[currentMove]);
15239     }
15240   }
15241 }
15242
15243 static char cseq[12] = "\\   ";
15244
15245 Boolean set_cont_sequence(char *new_seq)
15246 {
15247     int len;
15248     Boolean ret;
15249
15250     // handle bad attempts to set the sequence
15251         if (!new_seq)
15252                 return 0; // acceptable error - no debug
15253
15254     len = strlen(new_seq);
15255     ret = (len > 0) && (len < sizeof(cseq));
15256     if (ret)
15257       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15258     else if (appData.debugMode)
15259       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15260     return ret;
15261 }
15262
15263 /*
15264     reformat a source message so words don't cross the width boundary.  internal
15265     newlines are not removed.  returns the wrapped size (no null character unless
15266     included in source message).  If dest is NULL, only calculate the size required
15267     for the dest buffer.  lp argument indicats line position upon entry, and it's
15268     passed back upon exit.
15269 */
15270 int wrap(char *dest, char *src, int count, int width, int *lp)
15271 {
15272     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15273
15274     cseq_len = strlen(cseq);
15275     old_line = line = *lp;
15276     ansi = len = clen = 0;
15277
15278     for (i=0; i < count; i++)
15279     {
15280         if (src[i] == '\033')
15281             ansi = 1;
15282
15283         // if we hit the width, back up
15284         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15285         {
15286             // store i & len in case the word is too long
15287             old_i = i, old_len = len;
15288
15289             // find the end of the last word
15290             while (i && src[i] != ' ' && src[i] != '\n')
15291             {
15292                 i--;
15293                 len--;
15294             }
15295
15296             // word too long?  restore i & len before splitting it
15297             if ((old_i-i+clen) >= width)
15298             {
15299                 i = old_i;
15300                 len = old_len;
15301             }
15302
15303             // extra space?
15304             if (i && src[i-1] == ' ')
15305                 len--;
15306
15307             if (src[i] != ' ' && src[i] != '\n')
15308             {
15309                 i--;
15310                 if (len)
15311                     len--;
15312             }
15313
15314             // now append the newline and continuation sequence
15315             if (dest)
15316                 dest[len] = '\n';
15317             len++;
15318             if (dest)
15319                 strncpy(dest+len, cseq, cseq_len);
15320             len += cseq_len;
15321             line = cseq_len;
15322             clen = cseq_len;
15323             continue;
15324         }
15325
15326         if (dest)
15327             dest[len] = src[i];
15328         len++;
15329         if (!ansi)
15330             line++;
15331         if (src[i] == '\n')
15332             line = 0;
15333         if (src[i] == 'm')
15334             ansi = 0;
15335     }
15336     if (dest && appData.debugMode)
15337     {
15338         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15339             count, width, line, len, *lp);
15340         show_bytes(debugFP, src, count);
15341         fprintf(debugFP, "\ndest: ");
15342         show_bytes(debugFP, dest, len);
15343         fprintf(debugFP, "\n");
15344     }
15345     *lp = dest ? line : old_line;
15346
15347     return len;
15348 }
15349
15350 // [HGM] vari: routines for shelving variations
15351
15352 void
15353 PushTail(int firstMove, int lastMove)
15354 {
15355         int i, j, nrMoves = lastMove - firstMove;
15356
15357         if(appData.icsActive) { // only in local mode
15358                 forwardMostMove = currentMove; // mimic old ICS behavior
15359                 return;
15360         }
15361         if(storedGames >= MAX_VARIATIONS-1) return;
15362
15363         // push current tail of game on stack
15364         savedResult[storedGames] = gameInfo.result;
15365         savedDetails[storedGames] = gameInfo.resultDetails;
15366         gameInfo.resultDetails = NULL;
15367         savedFirst[storedGames] = firstMove;
15368         savedLast [storedGames] = lastMove;
15369         savedFramePtr[storedGames] = framePtr;
15370         framePtr -= nrMoves; // reserve space for the boards
15371         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15372             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15373             for(j=0; j<MOVE_LEN; j++)
15374                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15375             for(j=0; j<2*MOVE_LEN; j++)
15376                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15377             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15378             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15379             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15380             pvInfoList[firstMove+i-1].depth = 0;
15381             commentList[framePtr+i] = commentList[firstMove+i];
15382             commentList[firstMove+i] = NULL;
15383         }
15384
15385         storedGames++;
15386         forwardMostMove = firstMove; // truncate game so we can start variation
15387         if(storedGames == 1) GreyRevert(FALSE);
15388 }
15389
15390 Boolean
15391 PopTail(Boolean annotate)
15392 {
15393         int i, j, nrMoves;
15394         char buf[8000], moveBuf[20];
15395
15396         if(appData.icsActive) return FALSE; // only in local mode
15397         if(!storedGames) return FALSE; // sanity
15398         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15399
15400         storedGames--;
15401         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15402         nrMoves = savedLast[storedGames] - currentMove;
15403         if(annotate) {
15404                 int cnt = 10;
15405                 if(!WhiteOnMove(currentMove))
15406                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15407                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15408                 for(i=currentMove; i<forwardMostMove; i++) {
15409                         if(WhiteOnMove(i))
15410                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15411                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15412                         strcat(buf, moveBuf);
15413                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15414                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15415                 }
15416                 strcat(buf, ")");
15417         }
15418         for(i=1; i<=nrMoves; i++) { // copy last variation back
15419             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15420             for(j=0; j<MOVE_LEN; j++)
15421                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15422             for(j=0; j<2*MOVE_LEN; j++)
15423                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15424             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15425             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15426             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15427             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15428             commentList[currentMove+i] = commentList[framePtr+i];
15429             commentList[framePtr+i] = NULL;
15430         }
15431         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15432         framePtr = savedFramePtr[storedGames];
15433         gameInfo.result = savedResult[storedGames];
15434         if(gameInfo.resultDetails != NULL) {
15435             free(gameInfo.resultDetails);
15436       }
15437         gameInfo.resultDetails = savedDetails[storedGames];
15438         forwardMostMove = currentMove + nrMoves;
15439         if(storedGames == 0) GreyRevert(TRUE);
15440         return TRUE;
15441 }
15442
15443 void
15444 CleanupTail()
15445 {       // remove all shelved variations
15446         int i;
15447         for(i=0; i<storedGames; i++) {
15448             if(savedDetails[i])
15449                 free(savedDetails[i]);
15450             savedDetails[i] = NULL;
15451         }
15452         for(i=framePtr; i<MAX_MOVES; i++) {
15453                 if(commentList[i]) free(commentList[i]);
15454                 commentList[i] = NULL;
15455         }
15456         framePtr = MAX_MOVES-1;
15457         storedGames = 0;
15458 }
15459
15460 void
15461 LoadVariation(int index, char *text)
15462 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15463         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15464         int level = 0, move;
15465
15466         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15467         // first find outermost bracketing variation
15468         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15469             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15470                 if(*p == '{') wait = '}'; else
15471                 if(*p == '[') wait = ']'; else
15472                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15473                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15474             }
15475             if(*p == wait) wait = NULLCHAR; // closing ]} found
15476             p++;
15477         }
15478         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15479         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15480         end[1] = NULLCHAR; // clip off comment beyond variation
15481         ToNrEvent(currentMove-1);
15482         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15483         // kludge: use ParsePV() to append variation to game
15484         move = currentMove;
15485         ParsePV(start, TRUE);
15486         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15487         ClearPremoveHighlights();
15488         CommentPopDown();
15489         ToNrEvent(currentMove+1);
15490 }
15491