361fb86cfe693750d17656a67d1dd2e65cb7306f
[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 sending_ICS_login    = 0;
452 int sending_ICS_password = 0;
453
454 int movesPerSession;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
460 int matchGame = 0;
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
464
465 /* animateTraining preserves the state of appData.animate
466  * when Training mode is activated. This allows the
467  * response to be animated when appData.animate == TRUE and
468  * appData.animateDragging == TRUE.
469  */
470 Boolean animateTraining;
471
472 GameInfo gameInfo;
473
474 AppData appData;
475
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char  initialRights[BOARD_FILES];
480 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int   initialRulePlies, FENrulePlies;
482 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
483 int loadFlag = 0;
484 int shuffleOpenings;
485 int mute; // mute all sounds
486
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
490 int storedGames = 0;
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
496
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
500
501 ChessSquare  FIDEArray[2][BOARD_FILES] = {
502     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505         BlackKing, BlackBishop, BlackKnight, BlackRook }
506 };
507
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512         BlackKing, BlackKing, BlackKnight, BlackRook }
513 };
514
515 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
516     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518     { BlackRook, BlackMan, BlackBishop, BlackQueen,
519         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
520 };
521
522 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
523     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
526         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
527 };
528
529 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
530     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
531         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
532     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
533         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
534 };
535
536 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
538         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
539     { BlackRook, BlackKnight, BlackMan, BlackFerz,
540         BlackKing, BlackMan, BlackKnight, BlackRook }
541 };
542
543
544 #if (BOARD_FILES>=10)
545 ChessSquare ShogiArray[2][BOARD_FILES] = {
546     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
547         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
548     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
549         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
550 };
551
552 ChessSquare XiangqiArray[2][BOARD_FILES] = {
553     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
554         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
555     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
556         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
557 };
558
559 ChessSquare CapablancaArray[2][BOARD_FILES] = {
560     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
561         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
562     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
563         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
564 };
565
566 ChessSquare GreatArray[2][BOARD_FILES] = {
567     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
568         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
569     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
570         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
571 };
572
573 ChessSquare JanusArray[2][BOARD_FILES] = {
574     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
575         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
576     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
577         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
578 };
579
580 #ifdef GOTHIC
581 ChessSquare GothicArray[2][BOARD_FILES] = {
582     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
583         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
584     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
585         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
586 };
587 #else // !GOTHIC
588 #define GothicArray CapablancaArray
589 #endif // !GOTHIC
590
591 #ifdef FALCON
592 ChessSquare FalconArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
594         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
596         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
597 };
598 #else // !FALCON
599 #define FalconArray CapablancaArray
600 #endif // !FALCON
601
602 #else // !(BOARD_FILES>=10)
603 #define XiangqiPosition FIDEArray
604 #define CapablancaArray FIDEArray
605 #define GothicArray FIDEArray
606 #define GreatArray FIDEArray
607 #endif // !(BOARD_FILES>=10)
608
609 #if (BOARD_FILES>=12)
610 ChessSquare CourierArray[2][BOARD_FILES] = {
611     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
612         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
613     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
614         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
615 };
616 #else // !(BOARD_FILES>=12)
617 #define CourierArray CapablancaArray
618 #endif // !(BOARD_FILES>=12)
619
620
621 Board initialPosition;
622
623
624 /* Convert str to a rating. Checks for special cases of "----",
625
626    "++++", etc. Also strips ()'s */
627 int
628 string_to_rating(str)
629   char *str;
630 {
631   while(*str && !isdigit(*str)) ++str;
632   if (!*str)
633     return 0;   /* One of the special "no rating" cases */
634   else
635     return atoi(str);
636 }
637
638 void
639 ClearProgramStats()
640 {
641     /* Init programStats */
642     programStats.movelist[0] = 0;
643     programStats.depth = 0;
644     programStats.nr_moves = 0;
645     programStats.moves_left = 0;
646     programStats.nodes = 0;
647     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
648     programStats.score = 0;
649     programStats.got_only_move = 0;
650     programStats.got_fail = 0;
651     programStats.line_is_book = 0;
652 }
653
654 void
655 InitBackEnd1()
656 {
657     int matched, min, sec;
658
659     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
660     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
661
662     GetTimeMark(&programStartTime);
663     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
664
665     ClearProgramStats();
666     programStats.ok_to_send = 1;
667     programStats.seen_stat = 0;
668
669     /*
670      * Initialize game list
671      */
672     ListNew(&gameList);
673
674
675     /*
676      * Internet chess server status
677      */
678     if (appData.icsActive) {
679         appData.matchMode = FALSE;
680         appData.matchGames = 0;
681 #if ZIPPY
682         appData.noChessProgram = !appData.zippyPlay;
683 #else
684         appData.zippyPlay = FALSE;
685         appData.zippyTalk = FALSE;
686         appData.noChessProgram = TRUE;
687 #endif
688         if (*appData.icsHelper != NULLCHAR) {
689             appData.useTelnet = TRUE;
690             appData.telnetProgram = appData.icsHelper;
691         }
692     } else {
693         appData.zippyTalk = appData.zippyPlay = FALSE;
694     }
695
696     /* [AS] Initialize pv info list [HGM] and game state */
697     {
698         int i, j;
699
700         for( i=0; i<=framePtr; i++ ) {
701             pvInfoList[i].depth = -1;
702             boards[i][EP_STATUS] = EP_NONE;
703             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
704         }
705     }
706
707     /*
708      * Parse timeControl resource
709      */
710     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
711                           appData.movesPerSession)) {
712         char buf[MSG_SIZ];
713         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
714         DisplayFatalError(buf, 0, 2);
715     }
716
717     /*
718      * Parse searchTime resource
719      */
720     if (*appData.searchTime != NULLCHAR) {
721         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
722         if (matched == 1) {
723             searchTime = min * 60;
724         } else if (matched == 2) {
725             searchTime = min * 60 + sec;
726         } else {
727             char buf[MSG_SIZ];
728             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
729             DisplayFatalError(buf, 0, 2);
730         }
731     }
732
733     /* [AS] Adjudication threshold */
734     adjudicateLossThreshold = appData.adjudicateLossThreshold;
735
736     first.which = _("first");
737     second.which = _("second");
738     first.maybeThinking = second.maybeThinking = FALSE;
739     first.pr = second.pr = NoProc;
740     first.isr = second.isr = NULL;
741     first.sendTime = second.sendTime = 2;
742     first.sendDrawOffers = 1;
743     if (appData.firstPlaysBlack) {
744         first.twoMachinesColor = "black\n";
745         second.twoMachinesColor = "white\n";
746     } else {
747         first.twoMachinesColor = "white\n";
748         second.twoMachinesColor = "black\n";
749     }
750     first.program = appData.firstChessProgram;
751     second.program = appData.secondChessProgram;
752     first.host = appData.firstHost;
753     second.host = appData.secondHost;
754     first.dir = appData.firstDirectory;
755     second.dir = appData.secondDirectory;
756     first.other = &second;
757     second.other = &first;
758     first.initString = appData.initString;
759     second.initString = appData.secondInitString;
760     first.computerString = appData.firstComputerString;
761     second.computerString = appData.secondComputerString;
762     first.useSigint = second.useSigint = TRUE;
763     first.useSigterm = second.useSigterm = TRUE;
764     first.reuse = appData.reuseFirst;
765     second.reuse = appData.reuseSecond;
766     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
767     second.nps = appData.secondNPS;
768     first.useSetboard = second.useSetboard = FALSE;
769     first.useSAN = second.useSAN = FALSE;
770     first.usePing = second.usePing = FALSE;
771     first.lastPing = second.lastPing = 0;
772     first.lastPong = second.lastPong = 0;
773     first.usePlayother = second.usePlayother = FALSE;
774     first.useColors = second.useColors = TRUE;
775     first.useUsermove = second.useUsermove = FALSE;
776     first.sendICS = second.sendICS = FALSE;
777     first.sendName = second.sendName = appData.icsActive;
778     first.sdKludge = second.sdKludge = FALSE;
779     first.stKludge = second.stKludge = FALSE;
780     TidyProgramName(first.program, first.host, first.tidy);
781     TidyProgramName(second.program, second.host, second.tidy);
782     first.matchWins = second.matchWins = 0;
783     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
784     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
785     first.analysisSupport = second.analysisSupport = 2; /* detect */
786     first.analyzing = second.analyzing = FALSE;
787     first.initDone = second.initDone = FALSE;
788
789     /* New features added by Tord: */
790     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
791     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
792     /* End of new features added by Tord. */
793     first.fenOverride  = appData.fenOverride1;
794     second.fenOverride = appData.fenOverride2;
795
796     /* [HGM] time odds: set factor for each machine */
797     first.timeOdds  = appData.firstTimeOdds;
798     second.timeOdds = appData.secondTimeOdds;
799     { float norm = 1;
800         if(appData.timeOddsMode) {
801             norm = first.timeOdds;
802             if(norm > second.timeOdds) norm = second.timeOdds;
803         }
804         first.timeOdds /= norm;
805         second.timeOdds /= norm;
806     }
807
808     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
809     first.accumulateTC = appData.firstAccumulateTC;
810     second.accumulateTC = appData.secondAccumulateTC;
811     first.maxNrOfSessions = second.maxNrOfSessions = 1;
812
813     /* [HGM] debug */
814     first.debug = second.debug = FALSE;
815     first.supportsNPS = second.supportsNPS = UNKNOWN;
816
817     /* [HGM] options */
818     first.optionSettings  = appData.firstOptions;
819     second.optionSettings = appData.secondOptions;
820
821     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
822     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
823     first.isUCI = appData.firstIsUCI; /* [AS] */
824     second.isUCI = appData.secondIsUCI; /* [AS] */
825     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
826     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
827
828     if (appData.firstProtocolVersion > PROTOVER
829         || appData.firstProtocolVersion < 1)
830       {
831         char buf[MSG_SIZ];
832         int len;
833
834         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
835                        appData.firstProtocolVersion);
836         if( (len > MSG_SIZ) && appData.debugMode )
837           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
838
839         DisplayFatalError(buf, 0, 2);
840       }
841     else
842       {
843         first.protocolVersion = appData.firstProtocolVersion;
844       }
845
846     if (appData.secondProtocolVersion > PROTOVER
847         || appData.secondProtocolVersion < 1)
848       {
849         char buf[MSG_SIZ];
850         int len;
851
852         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
853                        appData.secondProtocolVersion);
854         if( (len > MSG_SIZ) && appData.debugMode )
855           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
856
857         DisplayFatalError(buf, 0, 2);
858       }
859     else
860       {
861         second.protocolVersion = appData.secondProtocolVersion;
862       }
863
864     if (appData.icsActive) {
865         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
866 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
867     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
868         appData.clockMode = FALSE;
869         first.sendTime = second.sendTime = 0;
870     }
871
872 #if ZIPPY
873     /* Override some settings from environment variables, for backward
874        compatibility.  Unfortunately it's not feasible to have the env
875        vars just set defaults, at least in xboard.  Ugh.
876     */
877     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
878       ZippyInit();
879     }
880 #endif
881
882     if (appData.noChessProgram) {
883         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
884         sprintf(programVersion, "%s", PACKAGE_STRING);
885     } else {
886       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
887       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
888       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
889     }
890
891     if (!appData.icsActive) {
892       char buf[MSG_SIZ];
893       int len;
894
895       /* Check for variants that are supported only in ICS mode,
896          or not at all.  Some that are accepted here nevertheless
897          have bugs; see comments below.
898       */
899       VariantClass variant = StringToVariant(appData.variant);
900       switch (variant) {
901       case VariantBughouse:     /* need four players and two boards */
902       case VariantKriegspiel:   /* need to hide pieces and move details */
903         /* case VariantFischeRandom: (Fabien: moved below) */
904         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
905         if( (len > MSG_SIZ) && appData.debugMode )
906           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
907
908         DisplayFatalError(buf, 0, 2);
909         return;
910
911       case VariantUnknown:
912       case VariantLoadable:
913       case Variant29:
914       case Variant30:
915       case Variant31:
916       case Variant32:
917       case Variant33:
918       case Variant34:
919       case Variant35:
920       case Variant36:
921       default:
922         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
923         if( (len > MSG_SIZ) && appData.debugMode )
924           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
925
926         DisplayFatalError(buf, 0, 2);
927         return;
928
929       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
930       case VariantFairy:      /* [HGM] TestLegality definitely off! */
931       case VariantGothic:     /* [HGM] should work */
932       case VariantCapablanca: /* [HGM] should work */
933       case VariantCourier:    /* [HGM] initial forced moves not implemented */
934       case VariantShogi:      /* [HGM] could still mate with pawn drop */
935       case VariantKnightmate: /* [HGM] should work */
936       case VariantCylinder:   /* [HGM] untested */
937       case VariantFalcon:     /* [HGM] untested */
938       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
939                                  offboard interposition not understood */
940       case VariantNormal:     /* definitely works! */
941       case VariantWildCastle: /* pieces not automatically shuffled */
942       case VariantNoCastle:   /* pieces not automatically shuffled */
943       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
944       case VariantLosers:     /* should work except for win condition,
945                                  and doesn't know captures are mandatory */
946       case VariantSuicide:    /* should work except for win condition,
947                                  and doesn't know captures are mandatory */
948       case VariantGiveaway:   /* should work except for win condition,
949                                  and doesn't know captures are mandatory */
950       case VariantTwoKings:   /* should work */
951       case VariantAtomic:     /* should work except for win condition */
952       case Variant3Check:     /* should work except for win condition */
953       case VariantShatranj:   /* should work except for all win conditions */
954       case VariantMakruk:     /* should work except for daw countdown */
955       case VariantBerolina:   /* might work if TestLegality is off */
956       case VariantCapaRandom: /* should work */
957       case VariantJanus:      /* should work */
958       case VariantSuper:      /* experimental */
959       case VariantGreat:      /* experimental, requires legality testing to be off */
960       case VariantSChess:     /* S-Chess, should work */
961         break;
962       }
963     }
964
965     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
966     InitEngineUCI( installDir, &second );
967 }
968
969 int NextIntegerFromString( char ** str, long * value )
970 {
971     int result = -1;
972     char * s = *str;
973
974     while( *s == ' ' || *s == '\t' ) {
975         s++;
976     }
977
978     *value = 0;
979
980     if( *s >= '0' && *s <= '9' ) {
981         while( *s >= '0' && *s <= '9' ) {
982             *value = *value * 10 + (*s - '0');
983             s++;
984         }
985
986         result = 0;
987     }
988
989     *str = s;
990
991     return result;
992 }
993
994 int NextTimeControlFromString( char ** str, long * value )
995 {
996     long temp;
997     int result = NextIntegerFromString( str, &temp );
998
999     if( result == 0 ) {
1000         *value = temp * 60; /* Minutes */
1001         if( **str == ':' ) {
1002             (*str)++;
1003             result = NextIntegerFromString( str, &temp );
1004             *value += temp; /* Seconds */
1005         }
1006     }
1007
1008     return result;
1009 }
1010
1011 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1012 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1013     int result = -1, type = 0; long temp, temp2;
1014
1015     if(**str != ':') return -1; // old params remain in force!
1016     (*str)++;
1017     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1018     if( NextIntegerFromString( str, &temp ) ) return -1;
1019     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1020
1021     if(**str != '/') {
1022         /* time only: incremental or sudden-death time control */
1023         if(**str == '+') { /* increment follows; read it */
1024             (*str)++;
1025             if(**str == '!') type = *(*str)++; // Bronstein TC
1026             if(result = NextIntegerFromString( str, &temp2)) return -1;
1027             *inc = temp2 * 1000;
1028             if(**str == '.') { // read fraction of increment
1029                 char *start = ++(*str);
1030                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1031                 temp2 *= 1000;
1032                 while(start++ < *str) temp2 /= 10;
1033                 *inc += temp2;
1034             }
1035         } else *inc = 0;
1036         *moves = 0; *tc = temp * 1000; *incType = type;
1037         return 0;
1038     }
1039
1040     (*str)++; /* classical time control */
1041     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1042
1043     if(result == 0) {
1044         *moves = temp;
1045         *tc    = temp2 * 1000;
1046         *inc   = 0;
1047         *incType = type;
1048     }
1049     return result;
1050 }
1051
1052 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1053 {   /* [HGM] get time to add from the multi-session time-control string */
1054     int incType, moves=1; /* kludge to force reading of first session */
1055     long time, increment;
1056     char *s = tcString;
1057
1058     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1059     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1060     do {
1061         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1062         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1063         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1064         if(movenr == -1) return time;    /* last move before new session     */
1065         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1066         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1067         if(!moves) return increment;     /* current session is incremental   */
1068         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1069     } while(movenr >= -1);               /* try again for next session       */
1070
1071     return 0; // no new time quota on this move
1072 }
1073
1074 int
1075 ParseTimeControl(tc, ti, mps)
1076      char *tc;
1077      float ti;
1078      int mps;
1079 {
1080   long tc1;
1081   long tc2;
1082   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1083   int min, sec=0;
1084
1085   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1086   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1087       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1088   if(ti > 0) {
1089
1090     if(mps)
1091       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1092     else
1093       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1094   } else {
1095     if(mps)
1096       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1097     else
1098       snprintf(buf, MSG_SIZ, ":%s", mytc);
1099   }
1100   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1101
1102   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1103     return FALSE;
1104   }
1105
1106   if( *tc == '/' ) {
1107     /* Parse second time control */
1108     tc++;
1109
1110     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1111       return FALSE;
1112     }
1113
1114     if( tc2 == 0 ) {
1115       return FALSE;
1116     }
1117
1118     timeControl_2 = tc2 * 1000;
1119   }
1120   else {
1121     timeControl_2 = 0;
1122   }
1123
1124   if( tc1 == 0 ) {
1125     return FALSE;
1126   }
1127
1128   timeControl = tc1 * 1000;
1129
1130   if (ti >= 0) {
1131     timeIncrement = ti * 1000;  /* convert to ms */
1132     movesPerSession = 0;
1133   } else {
1134     timeIncrement = 0;
1135     movesPerSession = mps;
1136   }
1137   return TRUE;
1138 }
1139
1140 void
1141 InitBackEnd2()
1142 {
1143     if (appData.debugMode) {
1144         fprintf(debugFP, "%s\n", programVersion);
1145     }
1146
1147     set_cont_sequence(appData.wrapContSeq);
1148     if (appData.matchGames > 0) {
1149         appData.matchMode = TRUE;
1150     } else if (appData.matchMode) {
1151         appData.matchGames = 1;
1152     }
1153     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1154         appData.matchGames = appData.sameColorGames;
1155     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1156         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1157         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1158     }
1159     Reset(TRUE, FALSE);
1160     if (appData.noChessProgram || first.protocolVersion == 1) {
1161       InitBackEnd3();
1162     } else {
1163       /* kludge: allow timeout for initial "feature" commands */
1164       FreezeUI();
1165       DisplayMessage("", _("Starting chess program"));
1166       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1167     }
1168 }
1169
1170 void
1171 InitBackEnd3 P((void))
1172 {
1173     GameMode initialMode;
1174     char buf[MSG_SIZ];
1175     int err, len;
1176
1177     InitChessProgram(&first, startedFromSetupPosition);
1178
1179     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1180         free(programVersion);
1181         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1182         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1183     }
1184
1185     if (appData.icsActive) {
1186 #ifdef WIN32
1187         /* [DM] Make a console window if needed [HGM] merged ifs */
1188         ConsoleCreate();
1189 #endif
1190         err = establish();
1191         if (err != 0)
1192           {
1193             if (*appData.icsCommPort != NULLCHAR)
1194               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1195                              appData.icsCommPort);
1196             else
1197               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1198                         appData.icsHost, appData.icsPort);
1199
1200             if( (len > MSG_SIZ) && appData.debugMode )
1201               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1202
1203             DisplayFatalError(buf, err, 1);
1204             return;
1205         }
1206         SetICSMode();
1207         telnetISR =
1208           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1209         fromUserISR =
1210           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1211         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1212             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1213     } else if (appData.noChessProgram) {
1214         SetNCPMode();
1215     } else {
1216         SetGNUMode();
1217     }
1218
1219     if (*appData.cmailGameName != NULLCHAR) {
1220         SetCmailMode();
1221         OpenLoopback(&cmailPR);
1222         cmailISR =
1223           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1224     }
1225
1226     ThawUI();
1227     DisplayMessage("", "");
1228     if (StrCaseCmp(appData.initialMode, "") == 0) {
1229       initialMode = BeginningOfGame;
1230     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1231       initialMode = TwoMachinesPlay;
1232     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1233       initialMode = AnalyzeFile;
1234     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1235       initialMode = AnalyzeMode;
1236     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1237       initialMode = MachinePlaysWhite;
1238     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1239       initialMode = MachinePlaysBlack;
1240     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1241       initialMode = EditGame;
1242     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1243       initialMode = EditPosition;
1244     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1245       initialMode = Training;
1246     } else {
1247       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1248       if( (len > MSG_SIZ) && appData.debugMode )
1249         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1250
1251       DisplayFatalError(buf, 0, 2);
1252       return;
1253     }
1254
1255     if (appData.matchMode) {
1256         /* Set up machine vs. machine match */
1257         if (appData.noChessProgram) {
1258             DisplayFatalError(_("Can't have a match with no chess programs"),
1259                               0, 2);
1260             return;
1261         }
1262         matchMode = TRUE;
1263         matchGame = 1;
1264         if (*appData.loadGameFile != NULLCHAR) {
1265             int index = appData.loadGameIndex; // [HGM] autoinc
1266             if(index<0) lastIndex = index = 1;
1267             if (!LoadGameFromFile(appData.loadGameFile,
1268                                   index,
1269                                   appData.loadGameFile, FALSE)) {
1270                 DisplayFatalError(_("Bad game file"), 0, 1);
1271                 return;
1272             }
1273         } else if (*appData.loadPositionFile != NULLCHAR) {
1274             int index = appData.loadPositionIndex; // [HGM] autoinc
1275             if(index<0) lastIndex = index = 1;
1276             if (!LoadPositionFromFile(appData.loadPositionFile,
1277                                       index,
1278                                       appData.loadPositionFile)) {
1279                 DisplayFatalError(_("Bad position file"), 0, 1);
1280                 return;
1281             }
1282         }
1283         TwoMachinesEvent();
1284     } else if (*appData.cmailGameName != NULLCHAR) {
1285         /* Set up cmail mode */
1286         ReloadCmailMsgEvent(TRUE);
1287     } else {
1288         /* Set up other modes */
1289         if (initialMode == AnalyzeFile) {
1290           if (*appData.loadGameFile == NULLCHAR) {
1291             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1292             return;
1293           }
1294         }
1295         if (*appData.loadGameFile != NULLCHAR) {
1296             (void) LoadGameFromFile(appData.loadGameFile,
1297                                     appData.loadGameIndex,
1298                                     appData.loadGameFile, TRUE);
1299         } else if (*appData.loadPositionFile != NULLCHAR) {
1300             (void) LoadPositionFromFile(appData.loadPositionFile,
1301                                         appData.loadPositionIndex,
1302                                         appData.loadPositionFile);
1303             /* [HGM] try to make self-starting even after FEN load */
1304             /* to allow automatic setup of fairy variants with wtm */
1305             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1306                 gameMode = BeginningOfGame;
1307                 setboardSpoiledMachineBlack = 1;
1308             }
1309             /* [HGM] loadPos: make that every new game uses the setup */
1310             /* from file as long as we do not switch variant          */
1311             if(!blackPlaysFirst) {
1312                 startedFromPositionFile = TRUE;
1313                 CopyBoard(filePosition, boards[0]);
1314             }
1315         }
1316         if (initialMode == AnalyzeMode) {
1317           if (appData.noChessProgram) {
1318             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1319             return;
1320           }
1321           if (appData.icsActive) {
1322             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1323             return;
1324           }
1325           AnalyzeModeEvent();
1326         } else if (initialMode == AnalyzeFile) {
1327           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1328           ShowThinkingEvent();
1329           AnalyzeFileEvent();
1330           AnalysisPeriodicEvent(1);
1331         } else if (initialMode == MachinePlaysWhite) {
1332           if (appData.noChessProgram) {
1333             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1334                               0, 2);
1335             return;
1336           }
1337           if (appData.icsActive) {
1338             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1339                               0, 2);
1340             return;
1341           }
1342           MachineWhiteEvent();
1343         } else if (initialMode == MachinePlaysBlack) {
1344           if (appData.noChessProgram) {
1345             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1346                               0, 2);
1347             return;
1348           }
1349           if (appData.icsActive) {
1350             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1351                               0, 2);
1352             return;
1353           }
1354           MachineBlackEvent();
1355         } else if (initialMode == TwoMachinesPlay) {
1356           if (appData.noChessProgram) {
1357             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1358                               0, 2);
1359             return;
1360           }
1361           if (appData.icsActive) {
1362             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1363                               0, 2);
1364             return;
1365           }
1366           TwoMachinesEvent();
1367         } else if (initialMode == EditGame) {
1368           EditGameEvent();
1369         } else if (initialMode == EditPosition) {
1370           EditPositionEvent();
1371         } else if (initialMode == Training) {
1372           if (*appData.loadGameFile == NULLCHAR) {
1373             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1374             return;
1375           }
1376           TrainingEvent();
1377         }
1378     }
1379 }
1380
1381 /*
1382  * Establish will establish a contact to a remote host.port.
1383  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1384  *  used to talk to the host.
1385  * Returns 0 if okay, error code if not.
1386  */
1387 int
1388 establish()
1389 {
1390     char buf[MSG_SIZ];
1391
1392     if (*appData.icsCommPort != NULLCHAR) {
1393         /* Talk to the host through a serial comm port */
1394         return OpenCommPort(appData.icsCommPort, &icsPR);
1395
1396     } else if (*appData.gateway != NULLCHAR) {
1397         if (*appData.remoteShell == NULLCHAR) {
1398             /* Use the rcmd protocol to run telnet program on a gateway host */
1399             snprintf(buf, sizeof(buf), "%s %s %s",
1400                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1401             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1402
1403         } else {
1404             /* Use the rsh program to run telnet program on a gateway host */
1405             if (*appData.remoteUser == NULLCHAR) {
1406                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1407                         appData.gateway, appData.telnetProgram,
1408                         appData.icsHost, appData.icsPort);
1409             } else {
1410                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1411                         appData.remoteShell, appData.gateway,
1412                         appData.remoteUser, appData.telnetProgram,
1413                         appData.icsHost, appData.icsPort);
1414             }
1415             return StartChildProcess(buf, "", &icsPR);
1416
1417         }
1418     } else if (appData.useTelnet) {
1419         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1420
1421     } else {
1422         /* TCP socket interface differs somewhat between
1423            Unix and NT; handle details in the front end.
1424            */
1425         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1426     }
1427 }
1428
1429 void EscapeExpand(char *p, char *q)
1430 {       // [HGM] initstring: routine to shape up string arguments
1431         while(*p++ = *q++) if(p[-1] == '\\')
1432             switch(*q++) {
1433                 case 'n': p[-1] = '\n'; break;
1434                 case 'r': p[-1] = '\r'; break;
1435                 case 't': p[-1] = '\t'; break;
1436                 case '\\': p[-1] = '\\'; break;
1437                 case 0: *p = 0; return;
1438                 default: p[-1] = q[-1]; break;
1439             }
1440 }
1441
1442 void
1443 show_bytes(fp, buf, count)
1444      FILE *fp;
1445      char *buf;
1446      int count;
1447 {
1448     while (count--) {
1449         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1450             fprintf(fp, "\\%03o", *buf & 0xff);
1451         } else {
1452             putc(*buf, fp);
1453         }
1454         buf++;
1455     }
1456     fflush(fp);
1457 }
1458
1459 /* Returns an errno value */
1460 int
1461 OutputMaybeTelnet(pr, message, count, outError)
1462      ProcRef pr;
1463      char *message;
1464      int count;
1465      int *outError;
1466 {
1467     char buf[8192], *p, *q, *buflim;
1468     int left, newcount, outcount;
1469
1470     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1471         *appData.gateway != NULLCHAR) {
1472         if (appData.debugMode) {
1473             fprintf(debugFP, ">ICS: ");
1474             show_bytes(debugFP, message, count);
1475             fprintf(debugFP, "\n");
1476         }
1477         return OutputToProcess(pr, message, count, outError);
1478     }
1479
1480     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1481     p = message;
1482     q = buf;
1483     left = count;
1484     newcount = 0;
1485     while (left) {
1486         if (q >= buflim) {
1487             if (appData.debugMode) {
1488                 fprintf(debugFP, ">ICS: ");
1489                 show_bytes(debugFP, buf, newcount);
1490                 fprintf(debugFP, "\n");
1491             }
1492             outcount = OutputToProcess(pr, buf, newcount, outError);
1493             if (outcount < newcount) return -1; /* to be sure */
1494             q = buf;
1495             newcount = 0;
1496         }
1497         if (*p == '\n') {
1498             *q++ = '\r';
1499             newcount++;
1500         } else if (((unsigned char) *p) == TN_IAC) {
1501             *q++ = (char) TN_IAC;
1502             newcount ++;
1503         }
1504         *q++ = *p++;
1505         newcount++;
1506         left--;
1507     }
1508     if (appData.debugMode) {
1509         fprintf(debugFP, ">ICS: ");
1510         show_bytes(debugFP, buf, newcount);
1511         fprintf(debugFP, "\n");
1512     }
1513     outcount = OutputToProcess(pr, buf, newcount, outError);
1514     if (outcount < newcount) return -1; /* to be sure */
1515     return count;
1516 }
1517
1518 void
1519 read_from_player(isr, closure, message, count, error)
1520      InputSourceRef isr;
1521      VOIDSTAR closure;
1522      char *message;
1523      int count;
1524      int error;
1525 {
1526     int outError, outCount;
1527     static int gotEof = 0;
1528
1529     /* Pass data read from player on to ICS */
1530     if (count > 0) {
1531         gotEof = 0;
1532         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1533         if (outCount < count) {
1534             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1535         }
1536     } else if (count < 0) {
1537         RemoveInputSource(isr);
1538         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1539     } else if (gotEof++ > 0) {
1540         RemoveInputSource(isr);
1541         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1542     }
1543 }
1544
1545 void
1546 KeepAlive()
1547 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1548     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1549     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1550     SendToICS("date\n");
1551     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1552 }
1553
1554 /* added routine for printf style output to ics */
1555 void ics_printf(char *format, ...)
1556 {
1557     char buffer[MSG_SIZ];
1558     va_list args;
1559
1560     va_start(args, format);
1561     vsnprintf(buffer, sizeof(buffer), format, args);
1562     buffer[sizeof(buffer)-1] = '\0';
1563     SendToICS(buffer);
1564     va_end(args);
1565 }
1566
1567 void
1568 SendToICS(s)
1569      char *s;
1570 {
1571     int count, outCount, outError;
1572
1573     if (icsPR == NULL) return;
1574
1575     count = strlen(s);
1576     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1577     if (outCount < count) {
1578         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1579     }
1580 }
1581
1582 /* This is used for sending logon scripts to the ICS. Sending
1583    without a delay causes problems when using timestamp on ICC
1584    (at least on my machine). */
1585 void
1586 SendToICSDelayed(s,msdelay)
1587      char *s;
1588      long msdelay;
1589 {
1590     int count, outCount, outError;
1591
1592     if (icsPR == NULL) return;
1593
1594     count = strlen(s);
1595     if (appData.debugMode) {
1596         fprintf(debugFP, ">ICS: ");
1597         show_bytes(debugFP, s, count);
1598         fprintf(debugFP, "\n");
1599     }
1600     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1601                                       msdelay);
1602     if (outCount < count) {
1603         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1604     }
1605 }
1606
1607
1608 /* Remove all highlighting escape sequences in s
1609    Also deletes any suffix starting with '('
1610    */
1611 char *
1612 StripHighlightAndTitle(s)
1613      char *s;
1614 {
1615     static char retbuf[MSG_SIZ];
1616     char *p = retbuf;
1617
1618     while (*s != NULLCHAR) {
1619         while (*s == '\033') {
1620             while (*s != NULLCHAR && !isalpha(*s)) s++;
1621             if (*s != NULLCHAR) s++;
1622         }
1623         while (*s != NULLCHAR && *s != '\033') {
1624             if (*s == '(' || *s == '[') {
1625                 *p = NULLCHAR;
1626                 return retbuf;
1627             }
1628             *p++ = *s++;
1629         }
1630     }
1631     *p = NULLCHAR;
1632     return retbuf;
1633 }
1634
1635 /* Remove all highlighting escape sequences in s */
1636 char *
1637 StripHighlight(s)
1638      char *s;
1639 {
1640     static char retbuf[MSG_SIZ];
1641     char *p = retbuf;
1642
1643     while (*s != NULLCHAR) {
1644         while (*s == '\033') {
1645             while (*s != NULLCHAR && !isalpha(*s)) s++;
1646             if (*s != NULLCHAR) s++;
1647         }
1648         while (*s != NULLCHAR && *s != '\033') {
1649             *p++ = *s++;
1650         }
1651     }
1652     *p = NULLCHAR;
1653     return retbuf;
1654 }
1655
1656 char *variantNames[] = VARIANT_NAMES;
1657 char *
1658 VariantName(v)
1659      VariantClass v;
1660 {
1661     return variantNames[v];
1662 }
1663
1664
1665 /* Identify a variant from the strings the chess servers use or the
1666    PGN Variant tag names we use. */
1667 VariantClass
1668 StringToVariant(e)
1669      char *e;
1670 {
1671     char *p;
1672     int wnum = -1;
1673     VariantClass v = VariantNormal;
1674     int i, found = FALSE;
1675     char buf[MSG_SIZ];
1676     int len;
1677
1678     if (!e) return v;
1679
1680     /* [HGM] skip over optional board-size prefixes */
1681     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1682         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1683         while( *e++ != '_');
1684     }
1685
1686     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1687         v = VariantNormal;
1688         found = TRUE;
1689     } else
1690     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1691       if (StrCaseStr(e, variantNames[i])) {
1692         v = (VariantClass) i;
1693         found = TRUE;
1694         break;
1695       }
1696     }
1697
1698     if (!found) {
1699       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1700           || StrCaseStr(e, "wild/fr")
1701           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1702         v = VariantFischeRandom;
1703       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1704                  (i = 1, p = StrCaseStr(e, "w"))) {
1705         p += i;
1706         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1707         if (isdigit(*p)) {
1708           wnum = atoi(p);
1709         } else {
1710           wnum = -1;
1711         }
1712         switch (wnum) {
1713         case 0: /* FICS only, actually */
1714         case 1:
1715           /* Castling legal even if K starts on d-file */
1716           v = VariantWildCastle;
1717           break;
1718         case 2:
1719         case 3:
1720         case 4:
1721           /* Castling illegal even if K & R happen to start in
1722              normal positions. */
1723           v = VariantNoCastle;
1724           break;
1725         case 5:
1726         case 7:
1727         case 8:
1728         case 10:
1729         case 11:
1730         case 12:
1731         case 13:
1732         case 14:
1733         case 15:
1734         case 18:
1735         case 19:
1736           /* Castling legal iff K & R start in normal positions */
1737           v = VariantNormal;
1738           break;
1739         case 6:
1740         case 20:
1741         case 21:
1742           /* Special wilds for position setup; unclear what to do here */
1743           v = VariantLoadable;
1744           break;
1745         case 9:
1746           /* Bizarre ICC game */
1747           v = VariantTwoKings;
1748           break;
1749         case 16:
1750           v = VariantKriegspiel;
1751           break;
1752         case 17:
1753           v = VariantLosers;
1754           break;
1755         case 22:
1756           v = VariantFischeRandom;
1757           break;
1758         case 23:
1759           v = VariantCrazyhouse;
1760           break;
1761         case 24:
1762           v = VariantBughouse;
1763           break;
1764         case 25:
1765           v = Variant3Check;
1766           break;
1767         case 26:
1768           /* Not quite the same as FICS suicide! */
1769           v = VariantGiveaway;
1770           break;
1771         case 27:
1772           v = VariantAtomic;
1773           break;
1774         case 28:
1775           v = VariantShatranj;
1776           break;
1777
1778         /* Temporary names for future ICC types.  The name *will* change in
1779            the next xboard/WinBoard release after ICC defines it. */
1780         case 29:
1781           v = Variant29;
1782           break;
1783         case 30:
1784           v = Variant30;
1785           break;
1786         case 31:
1787           v = Variant31;
1788           break;
1789         case 32:
1790           v = Variant32;
1791           break;
1792         case 33:
1793           v = Variant33;
1794           break;
1795         case 34:
1796           v = Variant34;
1797           break;
1798         case 35:
1799           v = Variant35;
1800           break;
1801         case 36:
1802           v = Variant36;
1803           break;
1804         case 37:
1805           v = VariantShogi;
1806           break;
1807         case 38:
1808           v = VariantXiangqi;
1809           break;
1810         case 39:
1811           v = VariantCourier;
1812           break;
1813         case 40:
1814           v = VariantGothic;
1815           break;
1816         case 41:
1817           v = VariantCapablanca;
1818           break;
1819         case 42:
1820           v = VariantKnightmate;
1821           break;
1822         case 43:
1823           v = VariantFairy;
1824           break;
1825         case 44:
1826           v = VariantCylinder;
1827           break;
1828         case 45:
1829           v = VariantFalcon;
1830           break;
1831         case 46:
1832           v = VariantCapaRandom;
1833           break;
1834         case 47:
1835           v = VariantBerolina;
1836           break;
1837         case 48:
1838           v = VariantJanus;
1839           break;
1840         case 49:
1841           v = VariantSuper;
1842           break;
1843         case 50:
1844           v = VariantGreat;
1845           break;
1846         case -1:
1847           /* Found "wild" or "w" in the string but no number;
1848              must assume it's normal chess. */
1849           v = VariantNormal;
1850           break;
1851         default:
1852           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1853           if( (len > MSG_SIZ) && appData.debugMode )
1854             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1855
1856           DisplayError(buf, 0);
1857           v = VariantUnknown;
1858           break;
1859         }
1860       }
1861     }
1862     if (appData.debugMode) {
1863       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1864               e, wnum, VariantName(v));
1865     }
1866     return v;
1867 }
1868
1869 static int leftover_start = 0, leftover_len = 0;
1870 char star_match[STAR_MATCH_N][MSG_SIZ];
1871
1872 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1873    advance *index beyond it, and set leftover_start to the new value of
1874    *index; else return FALSE.  If pattern contains the character '*', it
1875    matches any sequence of characters not containing '\r', '\n', or the
1876    character following the '*' (if any), and the matched sequence(s) are
1877    copied into star_match.
1878    */
1879 int
1880 looking_at(buf, index, pattern)
1881      char *buf;
1882      int *index;
1883      char *pattern;
1884 {
1885     char *bufp = &buf[*index], *patternp = pattern;
1886     int star_count = 0;
1887     char *matchp = star_match[0];
1888
1889     for (;;) {
1890         if (*patternp == NULLCHAR) {
1891             *index = leftover_start = bufp - buf;
1892             *matchp = NULLCHAR;
1893             return TRUE;
1894         }
1895         if (*bufp == NULLCHAR) return FALSE;
1896         if (*patternp == '*') {
1897             if (*bufp == *(patternp + 1)) {
1898                 *matchp = NULLCHAR;
1899                 matchp = star_match[++star_count];
1900                 patternp += 2;
1901                 bufp++;
1902                 continue;
1903             } else if (*bufp == '\n' || *bufp == '\r') {
1904                 patternp++;
1905                 if (*patternp == NULLCHAR)
1906                   continue;
1907                 else
1908                   return FALSE;
1909             } else {
1910                 *matchp++ = *bufp++;
1911                 continue;
1912             }
1913         }
1914         if (*patternp != *bufp) return FALSE;
1915         patternp++;
1916         bufp++;
1917     }
1918 }
1919
1920 void
1921 SendToPlayer(data, length)
1922      char *data;
1923      int length;
1924 {
1925     int error, outCount;
1926     outCount = OutputToProcess(NoProc, data, length, &error);
1927     if (outCount < length) {
1928         DisplayFatalError(_("Error writing to display"), error, 1);
1929     }
1930 }
1931
1932 void
1933 PackHolding(packed, holding)
1934      char packed[];
1935      char *holding;
1936 {
1937     char *p = holding;
1938     char *q = packed;
1939     int runlength = 0;
1940     int curr = 9999;
1941     do {
1942         if (*p == curr) {
1943             runlength++;
1944         } else {
1945             switch (runlength) {
1946               case 0:
1947                 break;
1948               case 1:
1949                 *q++ = curr;
1950                 break;
1951               case 2:
1952                 *q++ = curr;
1953                 *q++ = curr;
1954                 break;
1955               default:
1956                 sprintf(q, "%d", runlength);
1957                 while (*q) q++;
1958                 *q++ = curr;
1959                 break;
1960             }
1961             runlength = 1;
1962             curr = *p;
1963         }
1964     } while (*p++);
1965     *q = NULLCHAR;
1966 }
1967
1968 /* Telnet protocol requests from the front end */
1969 void
1970 TelnetRequest(ddww, option)
1971      unsigned char ddww, option;
1972 {
1973     unsigned char msg[3];
1974     int outCount, outError;
1975
1976     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1977
1978     if (appData.debugMode) {
1979         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1980         switch (ddww) {
1981           case TN_DO:
1982             ddwwStr = "DO";
1983             break;
1984           case TN_DONT:
1985             ddwwStr = "DONT";
1986             break;
1987           case TN_WILL:
1988             ddwwStr = "WILL";
1989             break;
1990           case TN_WONT:
1991             ddwwStr = "WONT";
1992             break;
1993           default:
1994             ddwwStr = buf1;
1995             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1996             break;
1997         }
1998         switch (option) {
1999           case TN_ECHO:
2000             optionStr = "ECHO";
2001             break;
2002           default:
2003             optionStr = buf2;
2004             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2005             break;
2006         }
2007         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2008     }
2009     msg[0] = TN_IAC;
2010     msg[1] = ddww;
2011     msg[2] = option;
2012     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2013     if (outCount < 3) {
2014         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2015     }
2016 }
2017
2018 void
2019 DoEcho()
2020 {
2021     if (!appData.icsActive) return;
2022     TelnetRequest(TN_DO, TN_ECHO);
2023 }
2024
2025 void
2026 DontEcho()
2027 {
2028     if (!appData.icsActive) return;
2029     TelnetRequest(TN_DONT, TN_ECHO);
2030 }
2031
2032 void
2033 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2034 {
2035     /* put the holdings sent to us by the server on the board holdings area */
2036     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2037     char p;
2038     ChessSquare piece;
2039
2040     if(gameInfo.holdingsWidth < 2)  return;
2041     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2042         return; // prevent overwriting by pre-board holdings
2043
2044     if( (int)lowestPiece >= BlackPawn ) {
2045         holdingsColumn = 0;
2046         countsColumn = 1;
2047         holdingsStartRow = BOARD_HEIGHT-1;
2048         direction = -1;
2049     } else {
2050         holdingsColumn = BOARD_WIDTH-1;
2051         countsColumn = BOARD_WIDTH-2;
2052         holdingsStartRow = 0;
2053         direction = 1;
2054     }
2055
2056     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2057         board[i][holdingsColumn] = EmptySquare;
2058         board[i][countsColumn]   = (ChessSquare) 0;
2059     }
2060     while( (p=*holdings++) != NULLCHAR ) {
2061         piece = CharToPiece( ToUpper(p) );
2062         if(piece == EmptySquare) continue;
2063         /*j = (int) piece - (int) WhitePawn;*/
2064         j = PieceToNumber(piece);
2065         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2066         if(j < 0) continue;               /* should not happen */
2067         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2068         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2069         board[holdingsStartRow+j*direction][countsColumn]++;
2070     }
2071 }
2072
2073
2074 void
2075 VariantSwitch(Board board, VariantClass newVariant)
2076 {
2077    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2078    static Board oldBoard;
2079
2080    startedFromPositionFile = FALSE;
2081    if(gameInfo.variant == newVariant) return;
2082
2083    /* [HGM] This routine is called each time an assignment is made to
2084     * gameInfo.variant during a game, to make sure the board sizes
2085     * are set to match the new variant. If that means adding or deleting
2086     * holdings, we shift the playing board accordingly
2087     * This kludge is needed because in ICS observe mode, we get boards
2088     * of an ongoing game without knowing the variant, and learn about the
2089     * latter only later. This can be because of the move list we requested,
2090     * in which case the game history is refilled from the beginning anyway,
2091     * but also when receiving holdings of a crazyhouse game. In the latter
2092     * case we want to add those holdings to the already received position.
2093     */
2094
2095
2096    if (appData.debugMode) {
2097      fprintf(debugFP, "Switch board from %s to %s\n",
2098              VariantName(gameInfo.variant), VariantName(newVariant));
2099      setbuf(debugFP, NULL);
2100    }
2101    shuffleOpenings = 0;       /* [HGM] shuffle */
2102    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2103    switch(newVariant)
2104      {
2105      case VariantShogi:
2106        newWidth = 9;  newHeight = 9;
2107        gameInfo.holdingsSize = 7;
2108      case VariantBughouse:
2109      case VariantCrazyhouse:
2110        newHoldingsWidth = 2; break;
2111      case VariantGreat:
2112        newWidth = 10;
2113      case VariantSuper:
2114        newHoldingsWidth = 2;
2115        gameInfo.holdingsSize = 8;
2116        break;
2117      case VariantGothic:
2118      case VariantCapablanca:
2119      case VariantCapaRandom:
2120        newWidth = 10;
2121      default:
2122        newHoldingsWidth = gameInfo.holdingsSize = 0;
2123      };
2124
2125    if(newWidth  != gameInfo.boardWidth  ||
2126       newHeight != gameInfo.boardHeight ||
2127       newHoldingsWidth != gameInfo.holdingsWidth ) {
2128
2129      /* shift position to new playing area, if needed */
2130      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2131        for(i=0; i<BOARD_HEIGHT; i++)
2132          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2133            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2134              board[i][j];
2135        for(i=0; i<newHeight; i++) {
2136          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2137          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2138        }
2139      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2140        for(i=0; i<BOARD_HEIGHT; i++)
2141          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2142            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2143              board[i][j];
2144      }
2145      gameInfo.boardWidth  = newWidth;
2146      gameInfo.boardHeight = newHeight;
2147      gameInfo.holdingsWidth = newHoldingsWidth;
2148      gameInfo.variant = newVariant;
2149      InitDrawingSizes(-2, 0);
2150    } else gameInfo.variant = newVariant;
2151    CopyBoard(oldBoard, board);   // remember correctly formatted board
2152      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2153    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2154 }
2155
2156 static int loggedOn = FALSE;
2157
2158 /*-- Game start info cache: --*/
2159 int gs_gamenum;
2160 char gs_kind[MSG_SIZ];
2161 static char player1Name[128] = "";
2162 static char player2Name[128] = "";
2163 static char cont_seq[] = "\n\\   ";
2164 static int player1Rating = -1;
2165 static int player2Rating = -1;
2166 /*----------------------------*/
2167
2168 ColorClass curColor = ColorNormal;
2169 int suppressKibitz = 0;
2170
2171 // [HGM] seekgraph
2172 Boolean soughtPending = FALSE;
2173 Boolean seekGraphUp;
2174 #define MAX_SEEK_ADS 200
2175 #define SQUARE 0x80
2176 char *seekAdList[MAX_SEEK_ADS];
2177 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2178 float tcList[MAX_SEEK_ADS];
2179 char colorList[MAX_SEEK_ADS];
2180 int nrOfSeekAds = 0;
2181 int minRating = 1010, maxRating = 2800;
2182 int hMargin = 10, vMargin = 20, h, w;
2183 extern int squareSize, lineGap;
2184
2185 void
2186 PlotSeekAd(int i)
2187 {
2188         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2189         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2190         if(r < minRating+100 && r >=0 ) r = minRating+100;
2191         if(r > maxRating) r = maxRating;
2192         if(tc < 1.) tc = 1.;
2193         if(tc > 95.) tc = 95.;
2194         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2195         y = ((double)r - minRating)/(maxRating - minRating)
2196             * (h-vMargin-squareSize/8-1) + vMargin;
2197         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2198         if(strstr(seekAdList[i], " u ")) color = 1;
2199         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2200            !strstr(seekAdList[i], "bullet") &&
2201            !strstr(seekAdList[i], "blitz") &&
2202            !strstr(seekAdList[i], "standard") ) color = 2;
2203         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2204         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2205 }
2206
2207 void
2208 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2209 {
2210         char buf[MSG_SIZ], *ext = "";
2211         VariantClass v = StringToVariant(type);
2212         if(strstr(type, "wild")) {
2213             ext = type + 4; // append wild number
2214             if(v == VariantFischeRandom) type = "chess960"; else
2215             if(v == VariantLoadable) type = "setup"; else
2216             type = VariantName(v);
2217         }
2218         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2219         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2220             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2221             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2222             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2223             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2224             seekNrList[nrOfSeekAds] = nr;
2225             zList[nrOfSeekAds] = 0;
2226             seekAdList[nrOfSeekAds++] = StrSave(buf);
2227             if(plot) PlotSeekAd(nrOfSeekAds-1);
2228         }
2229 }
2230
2231 void
2232 EraseSeekDot(int i)
2233 {
2234     int x = xList[i], y = yList[i], d=squareSize/4, k;
2235     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2236     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2237     // now replot every dot that overlapped
2238     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2239         int xx = xList[k], yy = yList[k];
2240         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2241             DrawSeekDot(xx, yy, colorList[k]);
2242     }
2243 }
2244
2245 void
2246 RemoveSeekAd(int nr)
2247 {
2248         int i;
2249         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2250             EraseSeekDot(i);
2251             if(seekAdList[i]) free(seekAdList[i]);
2252             seekAdList[i] = seekAdList[--nrOfSeekAds];
2253             seekNrList[i] = seekNrList[nrOfSeekAds];
2254             ratingList[i] = ratingList[nrOfSeekAds];
2255             colorList[i]  = colorList[nrOfSeekAds];
2256             tcList[i] = tcList[nrOfSeekAds];
2257             xList[i]  = xList[nrOfSeekAds];
2258             yList[i]  = yList[nrOfSeekAds];
2259             zList[i]  = zList[nrOfSeekAds];
2260             seekAdList[nrOfSeekAds] = NULL;
2261             break;
2262         }
2263 }
2264
2265 Boolean
2266 MatchSoughtLine(char *line)
2267 {
2268     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2269     int nr, base, inc, u=0; char dummy;
2270
2271     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2272        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2273        (u=1) &&
2274        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2275         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2276         // match: compact and save the line
2277         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2278         return TRUE;
2279     }
2280     return FALSE;
2281 }
2282
2283 int
2284 DrawSeekGraph()
2285 {
2286     int i;
2287     if(!seekGraphUp) return FALSE;
2288     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2289     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2290
2291     DrawSeekBackground(0, 0, w, h);
2292     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2293     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2294     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2295         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2296         yy = h-1-yy;
2297         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2298         if(i%500 == 0) {
2299             char buf[MSG_SIZ];
2300             snprintf(buf, MSG_SIZ, "%d", i);
2301             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2302         }
2303     }
2304     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2305     for(i=1; i<100; i+=(i<10?1:5)) {
2306         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2307         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2308         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2309             char buf[MSG_SIZ];
2310             snprintf(buf, MSG_SIZ, "%d", i);
2311             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2312         }
2313     }
2314     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2315     return TRUE;
2316 }
2317
2318 int SeekGraphClick(ClickType click, int x, int y, int moving)
2319 {
2320     static int lastDown = 0, displayed = 0, lastSecond;
2321     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2322         if(click == Release || moving) return FALSE;
2323         nrOfSeekAds = 0;
2324         soughtPending = TRUE;
2325         SendToICS(ics_prefix);
2326         SendToICS("sought\n"); // should this be "sought all"?
2327     } else { // issue challenge based on clicked ad
2328         int dist = 10000; int i, closest = 0, second = 0;
2329         for(i=0; i<nrOfSeekAds; i++) {
2330             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2331             if(d < dist) { dist = d; closest = i; }
2332             second += (d - zList[i] < 120); // count in-range ads
2333             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2334         }
2335         if(dist < 120) {
2336             char buf[MSG_SIZ];
2337             second = (second > 1);
2338             if(displayed != closest || second != lastSecond) {
2339                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2340                 lastSecond = second; displayed = closest;
2341             }
2342             if(click == Press) {
2343                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2344                 lastDown = closest;
2345                 return TRUE;
2346             } // on press 'hit', only show info
2347             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2348             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2349             SendToICS(ics_prefix);
2350             SendToICS(buf);
2351             return TRUE; // let incoming board of started game pop down the graph
2352         } else if(click == Release) { // release 'miss' is ignored
2353             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2354             if(moving == 2) { // right up-click
2355                 nrOfSeekAds = 0; // refresh graph
2356                 soughtPending = TRUE;
2357                 SendToICS(ics_prefix);
2358                 SendToICS("sought\n"); // should this be "sought all"?
2359             }
2360             return TRUE;
2361         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2362         // press miss or release hit 'pop down' seek graph
2363         seekGraphUp = FALSE;
2364         DrawPosition(TRUE, NULL);
2365     }
2366     return TRUE;
2367 }
2368
2369 void
2370 read_from_ics(isr, closure, data, count, error)
2371      InputSourceRef isr;
2372      VOIDSTAR closure;
2373      char *data;
2374      int count;
2375      int error;
2376 {
2377 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2378 #define STARTED_NONE 0
2379 #define STARTED_MOVES 1
2380 #define STARTED_BOARD 2
2381 #define STARTED_OBSERVE 3
2382 #define STARTED_HOLDINGS 4
2383 #define STARTED_CHATTER 5
2384 #define STARTED_COMMENT 6
2385 #define STARTED_MOVES_NOHIDE 7
2386
2387     static int started = STARTED_NONE;
2388     static char parse[20000];
2389     static int parse_pos = 0;
2390     static char buf[BUF_SIZE + 1];
2391     static int firstTime = TRUE, intfSet = FALSE;
2392     static ColorClass prevColor = ColorNormal;
2393     static int savingComment = FALSE;
2394     static int cmatch = 0; // continuation sequence match
2395     char *bp;
2396     char str[MSG_SIZ];
2397     int i, oldi;
2398     int buf_len;
2399     int next_out;
2400     int tkind;
2401     int backup;    /* [DM] For zippy color lines */
2402     char *p;
2403     char talker[MSG_SIZ]; // [HGM] chat
2404     int channel;
2405
2406     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2407
2408     if (appData.debugMode) {
2409       if (!error) {
2410         fprintf(debugFP, "<ICS: ");
2411         show_bytes(debugFP, data, count);
2412         fprintf(debugFP, "\n");
2413       }
2414     }
2415
2416     if (appData.debugMode) { int f = forwardMostMove;
2417         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2418                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2419                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2420     }
2421     if (count > 0) {
2422         /* If last read ended with a partial line that we couldn't parse,
2423            prepend it to the new read and try again. */
2424         if (leftover_len > 0) {
2425             for (i=0; i<leftover_len; i++)
2426               buf[i] = buf[leftover_start + i];
2427         }
2428
2429     /* copy new characters into the buffer */
2430     bp = buf + leftover_len;
2431     buf_len=leftover_len;
2432     for (i=0; i<count; i++)
2433     {
2434         // ignore these
2435         if (data[i] == '\r')
2436             continue;
2437
2438         // join lines split by ICS?
2439         if (!appData.noJoin)
2440         {
2441             /*
2442                 Joining just consists of finding matches against the
2443                 continuation sequence, and discarding that sequence
2444                 if found instead of copying it.  So, until a match
2445                 fails, there's nothing to do since it might be the
2446                 complete sequence, and thus, something we don't want
2447                 copied.
2448             */
2449             if (data[i] == cont_seq[cmatch])
2450             {
2451                 cmatch++;
2452                 if (cmatch == strlen(cont_seq))
2453                 {
2454                     cmatch = 0; // complete match.  just reset the counter
2455
2456                     /*
2457                         it's possible for the ICS to not include the space
2458                         at the end of the last word, making our [correct]
2459                         join operation fuse two separate words.  the server
2460                         does this when the space occurs at the width setting.
2461                     */
2462                     if (!buf_len || buf[buf_len-1] != ' ')
2463                     {
2464                         *bp++ = ' ';
2465                         buf_len++;
2466                     }
2467                 }
2468                 continue;
2469             }
2470             else if (cmatch)
2471             {
2472                 /*
2473                     match failed, so we have to copy what matched before
2474                     falling through and copying this character.  In reality,
2475                     this will only ever be just the newline character, but
2476                     it doesn't hurt to be precise.
2477                 */
2478                 strncpy(bp, cont_seq, cmatch);
2479                 bp += cmatch;
2480                 buf_len += cmatch;
2481                 cmatch = 0;
2482             }
2483         }
2484
2485         // copy this char
2486         *bp++ = data[i];
2487         buf_len++;
2488     }
2489
2490         buf[buf_len] = NULLCHAR;
2491 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2492         next_out = 0;
2493         leftover_start = 0;
2494
2495         i = 0;
2496         while (i < buf_len) {
2497             /* Deal with part of the TELNET option negotiation
2498                protocol.  We refuse to do anything beyond the
2499                defaults, except that we allow the WILL ECHO option,
2500                which ICS uses to turn off password echoing when we are
2501                directly connected to it.  We reject this option
2502                if localLineEditing mode is on (always on in xboard)
2503                and we are talking to port 23, which might be a real
2504                telnet server that will try to keep WILL ECHO on permanently.
2505              */
2506             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2507                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2508                 unsigned char option;
2509                 oldi = i;
2510                 switch ((unsigned char) buf[++i]) {
2511                   case TN_WILL:
2512                     if (appData.debugMode)
2513                       fprintf(debugFP, "\n<WILL ");
2514                     switch (option = (unsigned char) buf[++i]) {
2515                       case TN_ECHO:
2516                         if (appData.debugMode)
2517                           fprintf(debugFP, "ECHO ");
2518                         /* Reply only if this is a change, according
2519                            to the protocol rules. */
2520                         if (remoteEchoOption) break;
2521                         if (appData.localLineEditing &&
2522                             atoi(appData.icsPort) == TN_PORT) {
2523                             TelnetRequest(TN_DONT, TN_ECHO);
2524                         } else {
2525                             EchoOff();
2526                             TelnetRequest(TN_DO, TN_ECHO);
2527                             remoteEchoOption = TRUE;
2528                         }
2529                         break;
2530                       default:
2531                         if (appData.debugMode)
2532                           fprintf(debugFP, "%d ", option);
2533                         /* Whatever this is, we don't want it. */
2534                         TelnetRequest(TN_DONT, option);
2535                         break;
2536                     }
2537                     break;
2538                   case TN_WONT:
2539                     if (appData.debugMode)
2540                       fprintf(debugFP, "\n<WONT ");
2541                     switch (option = (unsigned char) buf[++i]) {
2542                       case TN_ECHO:
2543                         if (appData.debugMode)
2544                           fprintf(debugFP, "ECHO ");
2545                         /* Reply only if this is a change, according
2546                            to the protocol rules. */
2547                         if (!remoteEchoOption) break;
2548                         EchoOn();
2549                         TelnetRequest(TN_DONT, TN_ECHO);
2550                         remoteEchoOption = FALSE;
2551                         break;
2552                       default:
2553                         if (appData.debugMode)
2554                           fprintf(debugFP, "%d ", (unsigned char) option);
2555                         /* Whatever this is, it must already be turned
2556                            off, because we never agree to turn on
2557                            anything non-default, so according to the
2558                            protocol rules, we don't reply. */
2559                         break;
2560                     }
2561                     break;
2562                   case TN_DO:
2563                     if (appData.debugMode)
2564                       fprintf(debugFP, "\n<DO ");
2565                     switch (option = (unsigned char) buf[++i]) {
2566                       default:
2567                         /* Whatever this is, we refuse to do it. */
2568                         if (appData.debugMode)
2569                           fprintf(debugFP, "%d ", option);
2570                         TelnetRequest(TN_WONT, option);
2571                         break;
2572                     }
2573                     break;
2574                   case TN_DONT:
2575                     if (appData.debugMode)
2576                       fprintf(debugFP, "\n<DONT ");
2577                     switch (option = (unsigned char) buf[++i]) {
2578                       default:
2579                         if (appData.debugMode)
2580                           fprintf(debugFP, "%d ", option);
2581                         /* Whatever this is, we are already not doing
2582                            it, because we never agree to do anything
2583                            non-default, so according to the protocol
2584                            rules, we don't reply. */
2585                         break;
2586                     }
2587                     break;
2588                   case TN_IAC:
2589                     if (appData.debugMode)
2590                       fprintf(debugFP, "\n<IAC ");
2591                     /* Doubled IAC; pass it through */
2592                     i--;
2593                     break;
2594                   default:
2595                     if (appData.debugMode)
2596                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2597                     /* Drop all other telnet commands on the floor */
2598                     break;
2599                 }
2600                 if (oldi > next_out)
2601                   SendToPlayer(&buf[next_out], oldi - next_out);
2602                 if (++i > next_out)
2603                   next_out = i;
2604                 continue;
2605             }
2606
2607             /* OK, this at least will *usually* work */
2608             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2609                 loggedOn = TRUE;
2610             }
2611
2612             if (loggedOn && !intfSet) {
2613                 if (ics_type == ICS_ICC) {
2614                   snprintf(str, MSG_SIZ,
2615                           "/set-quietly interface %s\n/set-quietly style 12\n",
2616                           programVersion);
2617                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2618                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2619                 } else if (ics_type == ICS_CHESSNET) {
2620                   snprintf(str, MSG_SIZ, "/style 12\n");
2621                 } else {
2622                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2623                   strcat(str, programVersion);
2624                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2625                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2626                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2627 #ifdef WIN32
2628                   strcat(str, "$iset nohighlight 1\n");
2629 #endif
2630                   strcat(str, "$iset lock 1\n$style 12\n");
2631                 }
2632                 SendToICS(str);
2633                 NotifyFrontendLogin();
2634                 intfSet = TRUE;
2635             }
2636
2637             if (started == STARTED_COMMENT) {
2638                 /* Accumulate characters in comment */
2639                 parse[parse_pos++] = buf[i];
2640                 if (buf[i] == '\n') {
2641                     parse[parse_pos] = NULLCHAR;
2642                     if(chattingPartner>=0) {
2643                         char mess[MSG_SIZ];
2644                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2645                         OutputChatMessage(chattingPartner, mess);
2646                         chattingPartner = -1;
2647                         next_out = i+1; // [HGM] suppress printing in ICS window
2648                     } else
2649                     if(!suppressKibitz) // [HGM] kibitz
2650                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2651                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2652                         int nrDigit = 0, nrAlph = 0, j;
2653                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2654                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2655                         parse[parse_pos] = NULLCHAR;
2656                         // try to be smart: if it does not look like search info, it should go to
2657                         // ICS interaction window after all, not to engine-output window.
2658                         for(j=0; j<parse_pos; j++) { // count letters and digits
2659                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2660                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2661                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2662                         }
2663                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2664                             int depth=0; float score;
2665                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2666                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2667                                 pvInfoList[forwardMostMove-1].depth = depth;
2668                                 pvInfoList[forwardMostMove-1].score = 100*score;
2669                             }
2670                             OutputKibitz(suppressKibitz, parse);
2671                         } else {
2672                             char tmp[MSG_SIZ];
2673                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2674                             SendToPlayer(tmp, strlen(tmp));
2675                         }
2676                         next_out = i+1; // [HGM] suppress printing in ICS window
2677                     }
2678                     started = STARTED_NONE;
2679                 } else {
2680                     /* Don't match patterns against characters in comment */
2681                     i++;
2682                     continue;
2683                 }
2684             }
2685             if (started == STARTED_CHATTER) {
2686                 if (buf[i] != '\n') {
2687                     /* Don't match patterns against characters in chatter */
2688                     i++;
2689                     continue;
2690                 }
2691                 started = STARTED_NONE;
2692                 if(suppressKibitz) next_out = i+1;
2693             }
2694
2695             /* Kludge to deal with rcmd protocol */
2696             if (firstTime && looking_at(buf, &i, "\001*")) {
2697                 DisplayFatalError(&buf[1], 0, 1);
2698                 continue;
2699             } else {
2700                 firstTime = FALSE;
2701             }
2702
2703             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2704                 ics_type = ICS_ICC;
2705                 ics_prefix = "/";
2706                 if (appData.debugMode)
2707                   fprintf(debugFP, "ics_type %d\n", ics_type);
2708                 continue;
2709             }
2710             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2711                 ics_type = ICS_FICS;
2712                 ics_prefix = "$";
2713                 if (appData.debugMode)
2714                   fprintf(debugFP, "ics_type %d\n", ics_type);
2715                 continue;
2716             }
2717             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2718                 ics_type = ICS_CHESSNET;
2719                 ics_prefix = "/";
2720                 if (appData.debugMode)
2721                   fprintf(debugFP, "ics_type %d\n", ics_type);
2722                 continue;
2723             }
2724
2725             if (!loggedOn &&
2726                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2727                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2728                  looking_at(buf, &i, "will be \"*\""))) {
2729               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2730               continue;
2731             }
2732
2733             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2734               char buf[MSG_SIZ];
2735               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2736               DisplayIcsInteractionTitle(buf);
2737               have_set_title = TRUE;
2738             }
2739
2740             /* skip finger notes */
2741             if (started == STARTED_NONE &&
2742                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2743                  (buf[i] == '1' && buf[i+1] == '0')) &&
2744                 buf[i+2] == ':' && buf[i+3] == ' ') {
2745               started = STARTED_CHATTER;
2746               i += 3;
2747               continue;
2748             }
2749
2750             oldi = i;
2751             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2752             if(appData.seekGraph) {
2753                 if(soughtPending && MatchSoughtLine(buf+i)) {
2754                     i = strstr(buf+i, "rated") - buf;
2755                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2756                     next_out = leftover_start = i;
2757                     started = STARTED_CHATTER;
2758                     suppressKibitz = TRUE;
2759                     continue;
2760                 }
2761                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2762                         && looking_at(buf, &i, "* ads displayed")) {
2763                     soughtPending = FALSE;
2764                     seekGraphUp = TRUE;
2765                     DrawSeekGraph();
2766                     continue;
2767                 }
2768                 if(appData.autoRefresh) {
2769                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2770                         int s = (ics_type == ICS_ICC); // ICC format differs
2771                         if(seekGraphUp)
2772                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2773                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2774                         looking_at(buf, &i, "*% "); // eat prompt
2775                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2776                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2777                         next_out = i; // suppress
2778                         continue;
2779                     }
2780                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2781                         char *p = star_match[0];
2782                         while(*p) {
2783                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2784                             while(*p && *p++ != ' '); // next
2785                         }
2786                         looking_at(buf, &i, "*% "); // eat prompt
2787                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2788                         next_out = i;
2789                         continue;
2790                     }
2791                 }
2792             }
2793
2794             /* skip formula vars */
2795             if (started == STARTED_NONE &&
2796                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2797               started = STARTED_CHATTER;
2798               i += 3;
2799               continue;
2800             }
2801
2802             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2803             if (appData.autoKibitz && started == STARTED_NONE &&
2804                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2805                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2806                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2807                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2808                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2809                         suppressKibitz = TRUE;
2810                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2811                         next_out = i;
2812                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2813                                 && (gameMode == IcsPlayingWhite)) ||
2814                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2815                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2816                             started = STARTED_CHATTER; // own kibitz we simply discard
2817                         else {
2818                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2819                             parse_pos = 0; parse[0] = NULLCHAR;
2820                             savingComment = TRUE;
2821                             suppressKibitz = gameMode != IcsObserving ? 2 :
2822                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2823                         }
2824                         continue;
2825                 } else
2826                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2827                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2828                          && atoi(star_match[0])) {
2829                     // suppress the acknowledgements of our own autoKibitz
2830                     char *p;
2831                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2832                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2833                     SendToPlayer(star_match[0], strlen(star_match[0]));
2834                     if(looking_at(buf, &i, "*% ")) // eat prompt
2835                         suppressKibitz = FALSE;
2836                     next_out = i;
2837                     continue;
2838                 }
2839             } // [HGM] kibitz: end of patch
2840
2841             // [HGM] chat: intercept tells by users for which we have an open chat window
2842             channel = -1;
2843             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2844                                            looking_at(buf, &i, "* whispers:") ||
2845                                            looking_at(buf, &i, "* kibitzes:") ||
2846                                            looking_at(buf, &i, "* shouts:") ||
2847                                            looking_at(buf, &i, "* c-shouts:") ||
2848                                            looking_at(buf, &i, "--> * ") ||
2849                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2850                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2851                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2852                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2853                 int p;
2854                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2855                 chattingPartner = -1;
2856
2857                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2858                 for(p=0; p<MAX_CHAT; p++) {
2859                     if(channel == atoi(chatPartner[p])) {
2860                     talker[0] = '['; strcat(talker, "] ");
2861                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2862                     chattingPartner = p; break;
2863                     }
2864                 } else
2865                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2866                 for(p=0; p<MAX_CHAT; p++) {
2867                     if(!strcmp("kibitzes", chatPartner[p])) {
2868                         talker[0] = '['; strcat(talker, "] ");
2869                         chattingPartner = p; break;
2870                     }
2871                 } else
2872                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2873                 for(p=0; p<MAX_CHAT; p++) {
2874                     if(!strcmp("whispers", chatPartner[p])) {
2875                         talker[0] = '['; strcat(talker, "] ");
2876                         chattingPartner = p; break;
2877                     }
2878                 } else
2879                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2880                   if(buf[i-8] == '-' && buf[i-3] == 't')
2881                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2882                     if(!strcmp("c-shouts", chatPartner[p])) {
2883                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2884                         chattingPartner = p; break;
2885                     }
2886                   }
2887                   if(chattingPartner < 0)
2888                   for(p=0; p<MAX_CHAT; p++) {
2889                     if(!strcmp("shouts", chatPartner[p])) {
2890                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2891                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2892                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2893                         chattingPartner = p; break;
2894                     }
2895                   }
2896                 }
2897                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2898                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2899                     talker[0] = 0; Colorize(ColorTell, FALSE);
2900                     chattingPartner = p; break;
2901                 }
2902                 if(chattingPartner<0) i = oldi; else {
2903                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2904                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2905                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2906                     started = STARTED_COMMENT;
2907                     parse_pos = 0; parse[0] = NULLCHAR;
2908                     savingComment = 3 + chattingPartner; // counts as TRUE
2909                     suppressKibitz = TRUE;
2910                     continue;
2911                 }
2912             } // [HGM] chat: end of patch
2913
2914             if (appData.zippyTalk || appData.zippyPlay) {
2915                 /* [DM] Backup address for color zippy lines */
2916                 backup = i;
2917 #if ZIPPY
2918                if (loggedOn == TRUE)
2919                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2920                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2921 #endif
2922             } // [DM] 'else { ' deleted
2923                 if (
2924                     /* Regular tells and says */
2925                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2926                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2927                     looking_at(buf, &i, "* says: ") ||
2928                     /* Don't color "message" or "messages" output */
2929                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2930                     looking_at(buf, &i, "*. * at *:*: ") ||
2931                     looking_at(buf, &i, "--* (*:*): ") ||
2932                     /* Message notifications (same color as tells) */
2933                     looking_at(buf, &i, "* has left a message ") ||
2934                     looking_at(buf, &i, "* just sent you a message:\n") ||
2935                     /* Whispers and kibitzes */
2936                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2937                     looking_at(buf, &i, "* kibitzes: ") ||
2938                     /* Channel tells */
2939                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2940
2941                   if (tkind == 1 && strchr(star_match[0], ':')) {
2942                       /* Avoid "tells you:" spoofs in channels */
2943                      tkind = 3;
2944                   }
2945                   if (star_match[0][0] == NULLCHAR ||
2946                       strchr(star_match[0], ' ') ||
2947                       (tkind == 3 && strchr(star_match[1], ' '))) {
2948                     /* Reject bogus matches */
2949                     i = oldi;
2950                   } else {
2951                     if (appData.colorize) {
2952                       if (oldi > next_out) {
2953                         SendToPlayer(&buf[next_out], oldi - next_out);
2954                         next_out = oldi;
2955                       }
2956                       switch (tkind) {
2957                       case 1:
2958                         Colorize(ColorTell, FALSE);
2959                         curColor = ColorTell;
2960                         break;
2961                       case 2:
2962                         Colorize(ColorKibitz, FALSE);
2963                         curColor = ColorKibitz;
2964                         break;
2965                       case 3:
2966                         p = strrchr(star_match[1], '(');
2967                         if (p == NULL) {
2968                           p = star_match[1];
2969                         } else {
2970                           p++;
2971                         }
2972                         if (atoi(p) == 1) {
2973                           Colorize(ColorChannel1, FALSE);
2974                           curColor = ColorChannel1;
2975                         } else {
2976                           Colorize(ColorChannel, FALSE);
2977                           curColor = ColorChannel;
2978                         }
2979                         break;
2980                       case 5:
2981                         curColor = ColorNormal;
2982                         break;
2983                       }
2984                     }
2985                     if (started == STARTED_NONE && appData.autoComment &&
2986                         (gameMode == IcsObserving ||
2987                          gameMode == IcsPlayingWhite ||
2988                          gameMode == IcsPlayingBlack)) {
2989                       parse_pos = i - oldi;
2990                       memcpy(parse, &buf[oldi], parse_pos);
2991                       parse[parse_pos] = NULLCHAR;
2992                       started = STARTED_COMMENT;
2993                       savingComment = TRUE;
2994                     } else {
2995                       started = STARTED_CHATTER;
2996                       savingComment = FALSE;
2997                     }
2998                     loggedOn = TRUE;
2999                     continue;
3000                   }
3001                 }
3002
3003                 if (looking_at(buf, &i, "* s-shouts: ") ||
3004                     looking_at(buf, &i, "* c-shouts: ")) {
3005                     if (appData.colorize) {
3006                         if (oldi > next_out) {
3007                             SendToPlayer(&buf[next_out], oldi - next_out);
3008                             next_out = oldi;
3009                         }
3010                         Colorize(ColorSShout, FALSE);
3011                         curColor = ColorSShout;
3012                     }
3013                     loggedOn = TRUE;
3014                     started = STARTED_CHATTER;
3015                     continue;
3016                 }
3017
3018                 if (looking_at(buf, &i, "--->")) {
3019                     loggedOn = TRUE;
3020                     continue;
3021                 }
3022
3023                 if (looking_at(buf, &i, "* shouts: ") ||
3024                     looking_at(buf, &i, "--> ")) {
3025                     if (appData.colorize) {
3026                         if (oldi > next_out) {
3027                             SendToPlayer(&buf[next_out], oldi - next_out);
3028                             next_out = oldi;
3029                         }
3030                         Colorize(ColorShout, FALSE);
3031                         curColor = ColorShout;
3032                     }
3033                     loggedOn = TRUE;
3034                     started = STARTED_CHATTER;
3035                     continue;
3036                 }
3037
3038                 if (looking_at( buf, &i, "Challenge:")) {
3039                     if (appData.colorize) {
3040                         if (oldi > next_out) {
3041                             SendToPlayer(&buf[next_out], oldi - next_out);
3042                             next_out = oldi;
3043                         }
3044                         Colorize(ColorChallenge, FALSE);
3045                         curColor = ColorChallenge;
3046                     }
3047                     loggedOn = TRUE;
3048                     continue;
3049                 }
3050
3051                 if (looking_at(buf, &i, "* offers you") ||
3052                     looking_at(buf, &i, "* offers to be") ||
3053                     looking_at(buf, &i, "* would like to") ||
3054                     looking_at(buf, &i, "* requests to") ||
3055                     looking_at(buf, &i, "Your opponent offers") ||
3056                     looking_at(buf, &i, "Your opponent requests")) {
3057
3058                     if (appData.colorize) {
3059                         if (oldi > next_out) {
3060                             SendToPlayer(&buf[next_out], oldi - next_out);
3061                             next_out = oldi;
3062                         }
3063                         Colorize(ColorRequest, FALSE);
3064                         curColor = ColorRequest;
3065                     }
3066                     continue;
3067                 }
3068
3069                 if (looking_at(buf, &i, "* (*) seeking")) {
3070                     if (appData.colorize) {
3071                         if (oldi > next_out) {
3072                             SendToPlayer(&buf[next_out], oldi - next_out);
3073                             next_out = oldi;
3074                         }
3075                         Colorize(ColorSeek, FALSE);
3076                         curColor = ColorSeek;
3077                     }
3078                     continue;
3079             }
3080
3081             if (looking_at(buf, &i, "\\   ")) {
3082                 if (prevColor != ColorNormal) {
3083                     if (oldi > next_out) {
3084                         SendToPlayer(&buf[next_out], oldi - next_out);
3085                         next_out = oldi;
3086                     }
3087                     Colorize(prevColor, TRUE);
3088                     curColor = prevColor;
3089                 }
3090                 if (savingComment) {
3091                     parse_pos = i - oldi;
3092                     memcpy(parse, &buf[oldi], parse_pos);
3093                     parse[parse_pos] = NULLCHAR;
3094                     started = STARTED_COMMENT;
3095                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3096                         chattingPartner = savingComment - 3; // kludge to remember the box
3097                 } else {
3098                     started = STARTED_CHATTER;
3099                 }
3100                 continue;
3101             }
3102
3103             if (looking_at(buf, &i, "Black Strength :") ||
3104                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3105                 looking_at(buf, &i, "<10>") ||
3106                 looking_at(buf, &i, "#@#")) {
3107                 /* Wrong board style */
3108                 loggedOn = TRUE;
3109                 SendToICS(ics_prefix);
3110                 SendToICS("set style 12\n");
3111                 SendToICS(ics_prefix);
3112                 SendToICS("refresh\n");
3113                 continue;
3114             }
3115
3116             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3117                 ICSInitScript();
3118                 have_sent_ICS_logon = 1;
3119                 /* if we don't send the login/password via icsLogon, use special readline
3120                    code for it */
3121                 if (strlen(appData.icsLogon)==0)
3122                   {
3123                     sending_ICS_password = 0; // in case we come back to login
3124                     sending_ICS_login = 1;
3125                   };
3126                 continue;
3127             }
3128             /* need to shadow the password */
3129             if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3130               /* if we don't send the login/password via icsLogon, use special readline
3131                  code for it */
3132               if (strlen(appData.icsLogon)==0)
3133                 sending_ICS_password = 1;
3134               continue;
3135             }
3136
3137             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3138                 (looking_at(buf, &i, "\n<12> ") ||
3139                  looking_at(buf, &i, "<12> "))) {
3140                 loggedOn = TRUE;
3141                 if (oldi > next_out) {
3142                     SendToPlayer(&buf[next_out], oldi - next_out);
3143                 }
3144                 next_out = i;
3145                 started = STARTED_BOARD;
3146                 parse_pos = 0;
3147                 continue;
3148             }
3149
3150             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3151                 looking_at(buf, &i, "<b1> ")) {
3152                 if (oldi > next_out) {
3153                     SendToPlayer(&buf[next_out], oldi - next_out);
3154                 }
3155                 next_out = i;
3156                 started = STARTED_HOLDINGS;
3157                 parse_pos = 0;
3158                 continue;
3159             }
3160
3161             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3162                 loggedOn = TRUE;
3163                 /* Header for a move list -- first line */
3164
3165                 switch (ics_getting_history) {
3166                   case H_FALSE:
3167                     switch (gameMode) {
3168                       case IcsIdle:
3169                       case BeginningOfGame:
3170                         /* User typed "moves" or "oldmoves" while we
3171                            were idle.  Pretend we asked for these
3172                            moves and soak them up so user can step
3173                            through them and/or save them.
3174                            */
3175                         Reset(FALSE, TRUE);
3176                         gameMode = IcsObserving;
3177                         ModeHighlight();
3178                         ics_gamenum = -1;
3179                         ics_getting_history = H_GOT_UNREQ_HEADER;
3180                         break;
3181                       case EditGame: /*?*/
3182                       case EditPosition: /*?*/
3183                         /* Should above feature work in these modes too? */
3184                         /* For now it doesn't */
3185                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3186                         break;
3187                       default:
3188                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3189                         break;
3190                     }
3191                     break;
3192                   case H_REQUESTED:
3193                     /* Is this the right one? */
3194                     if (gameInfo.white && gameInfo.black &&
3195                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3196                         strcmp(gameInfo.black, star_match[2]) == 0) {
3197                         /* All is well */
3198                         ics_getting_history = H_GOT_REQ_HEADER;
3199                     }
3200                     break;
3201                   case H_GOT_REQ_HEADER:
3202                   case H_GOT_UNREQ_HEADER:
3203                   case H_GOT_UNWANTED_HEADER:
3204                   case H_GETTING_MOVES:
3205                     /* Should not happen */
3206                     DisplayError(_("Error gathering move list: two headers"), 0);
3207                     ics_getting_history = H_FALSE;
3208                     break;
3209                 }
3210
3211                 /* Save player ratings into gameInfo if needed */
3212                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3213                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3214                     (gameInfo.whiteRating == -1 ||
3215                      gameInfo.blackRating == -1)) {
3216
3217                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3218                     gameInfo.blackRating = string_to_rating(star_match[3]);
3219                     if (appData.debugMode)
3220                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3221                               gameInfo.whiteRating, gameInfo.blackRating);
3222                 }
3223                 continue;
3224             }
3225
3226             if (looking_at(buf, &i,
3227               "* * match, initial time: * minute*, increment: * second")) {
3228                 /* Header for a move list -- second line */
3229                 /* Initial board will follow if this is a wild game */
3230                 if (gameInfo.event != NULL) free(gameInfo.event);
3231                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3232                 gameInfo.event = StrSave(str);
3233                 /* [HGM] we switched variant. Translate boards if needed. */
3234                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3235                 continue;
3236             }
3237
3238             if (looking_at(buf, &i, "Move  ")) {
3239                 /* Beginning of a move list */
3240                 switch (ics_getting_history) {
3241                   case H_FALSE:
3242                     /* Normally should not happen */
3243                     /* Maybe user hit reset while we were parsing */
3244                     break;
3245                   case H_REQUESTED:
3246                     /* Happens if we are ignoring a move list that is not
3247                      * the one we just requested.  Common if the user
3248                      * tries to observe two games without turning off
3249                      * getMoveList */
3250                     break;
3251                   case H_GETTING_MOVES:
3252                     /* Should not happen */
3253                     DisplayError(_("Error gathering move list: nested"), 0);
3254                     ics_getting_history = H_FALSE;
3255                     break;
3256                   case H_GOT_REQ_HEADER:
3257                     ics_getting_history = H_GETTING_MOVES;
3258                     started = STARTED_MOVES;
3259                     parse_pos = 0;
3260                     if (oldi > next_out) {
3261                         SendToPlayer(&buf[next_out], oldi - next_out);
3262                     }
3263                     break;
3264                   case H_GOT_UNREQ_HEADER:
3265                     ics_getting_history = H_GETTING_MOVES;
3266                     started = STARTED_MOVES_NOHIDE;
3267                     parse_pos = 0;
3268                     break;
3269                   case H_GOT_UNWANTED_HEADER:
3270                     ics_getting_history = H_FALSE;
3271                     break;
3272                 }
3273                 continue;
3274             }
3275
3276             if (looking_at(buf, &i, "% ") ||
3277                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3278                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3279                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3280                     soughtPending = FALSE;
3281                     seekGraphUp = TRUE;
3282                     DrawSeekGraph();
3283                 }
3284                 if(suppressKibitz) next_out = i;
3285                 savingComment = FALSE;
3286                 suppressKibitz = 0;
3287                 switch (started) {
3288                   case STARTED_MOVES:
3289                   case STARTED_MOVES_NOHIDE:
3290                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3291                     parse[parse_pos + i - oldi] = NULLCHAR;
3292                     ParseGameHistory(parse);
3293 #if ZIPPY
3294                     if (appData.zippyPlay && first.initDone) {
3295                         FeedMovesToProgram(&first, forwardMostMove);
3296                         if (gameMode == IcsPlayingWhite) {
3297                             if (WhiteOnMove(forwardMostMove)) {
3298                                 if (first.sendTime) {
3299                                   if (first.useColors) {
3300                                     SendToProgram("black\n", &first);
3301                                   }
3302                                   SendTimeRemaining(&first, TRUE);
3303                                 }
3304                                 if (first.useColors) {
3305                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3306                                 }
3307                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3308                                 first.maybeThinking = TRUE;
3309                             } else {
3310                                 if (first.usePlayother) {
3311                                   if (first.sendTime) {
3312                                     SendTimeRemaining(&first, TRUE);
3313                                   }
3314                                   SendToProgram("playother\n", &first);
3315                                   firstMove = FALSE;
3316                                 } else {
3317                                   firstMove = TRUE;
3318                                 }
3319                             }
3320                         } else if (gameMode == IcsPlayingBlack) {
3321                             if (!WhiteOnMove(forwardMostMove)) {
3322                                 if (first.sendTime) {
3323                                   if (first.useColors) {
3324                                     SendToProgram("white\n", &first);
3325                                   }
3326                                   SendTimeRemaining(&first, FALSE);
3327                                 }
3328                                 if (first.useColors) {
3329                                   SendToProgram("black\n", &first);
3330                                 }
3331                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3332                                 first.maybeThinking = TRUE;
3333                             } else {
3334                                 if (first.usePlayother) {
3335                                   if (first.sendTime) {
3336                                     SendTimeRemaining(&first, FALSE);
3337                                   }
3338                                   SendToProgram("playother\n", &first);
3339                                   firstMove = FALSE;
3340                                 } else {
3341                                   firstMove = TRUE;
3342                                 }
3343                             }
3344                         }
3345                     }
3346 #endif
3347                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3348                         /* Moves came from oldmoves or moves command
3349                            while we weren't doing anything else.
3350                            */
3351                         currentMove = forwardMostMove;
3352                         ClearHighlights();/*!!could figure this out*/
3353                         flipView = appData.flipView;
3354                         DrawPosition(TRUE, boards[currentMove]);
3355                         DisplayBothClocks();
3356                         snprintf(str, MSG_SIZ, "%s vs. %s",
3357                                 gameInfo.white, gameInfo.black);
3358                         DisplayTitle(str);
3359                         gameMode = IcsIdle;
3360                     } else {
3361                         /* Moves were history of an active game */
3362                         if (gameInfo.resultDetails != NULL) {
3363                             free(gameInfo.resultDetails);
3364                             gameInfo.resultDetails = NULL;
3365                         }
3366                     }
3367                     HistorySet(parseList, backwardMostMove,
3368                                forwardMostMove, currentMove-1);
3369                     DisplayMove(currentMove - 1);
3370                     if (started == STARTED_MOVES) next_out = i;
3371                     started = STARTED_NONE;
3372                     ics_getting_history = H_FALSE;
3373                     break;
3374
3375                   case STARTED_OBSERVE:
3376                     started = STARTED_NONE;
3377                     SendToICS(ics_prefix);
3378                     SendToICS("refresh\n");
3379                     break;
3380
3381                   default:
3382                     break;
3383                 }
3384                 if(bookHit) { // [HGM] book: simulate book reply
3385                     static char bookMove[MSG_SIZ]; // a bit generous?
3386
3387                     programStats.nodes = programStats.depth = programStats.time =
3388                     programStats.score = programStats.got_only_move = 0;
3389                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3390
3391                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3392                     strcat(bookMove, bookHit);
3393                     HandleMachineMove(bookMove, &first);
3394                 }
3395                 continue;
3396             }
3397
3398             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3399                  started == STARTED_HOLDINGS ||
3400                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3401                 /* Accumulate characters in move list or board */
3402                 parse[parse_pos++] = buf[i];
3403             }
3404
3405             /* Start of game messages.  Mostly we detect start of game
3406                when the first board image arrives.  On some versions
3407                of the ICS, though, we need to do a "refresh" after starting
3408                to observe in order to get the current board right away. */
3409             if (looking_at(buf, &i, "Adding game * to observation list")) {
3410                 started = STARTED_OBSERVE;
3411                 continue;
3412             }
3413
3414             /* Handle auto-observe */
3415             if (appData.autoObserve &&
3416                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3417                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3418                 char *player;
3419                 /* Choose the player that was highlighted, if any. */
3420                 if (star_match[0][0] == '\033' ||
3421                     star_match[1][0] != '\033') {
3422                     player = star_match[0];
3423                 } else {
3424                     player = star_match[2];
3425                 }
3426                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3427                         ics_prefix, StripHighlightAndTitle(player));
3428                 SendToICS(str);
3429
3430                 /* Save ratings from notify string */
3431                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3432                 player1Rating = string_to_rating(star_match[1]);
3433                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3434                 player2Rating = string_to_rating(star_match[3]);
3435
3436                 if (appData.debugMode)
3437                   fprintf(debugFP,
3438                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3439                           player1Name, player1Rating,
3440                           player2Name, player2Rating);
3441
3442                 continue;
3443             }
3444
3445             /* Deal with automatic examine mode after a game,
3446                and with IcsObserving -> IcsExamining transition */
3447             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3448                 looking_at(buf, &i, "has made you an examiner of game *")) {
3449
3450                 int gamenum = atoi(star_match[0]);
3451                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3452                     gamenum == ics_gamenum) {
3453                     /* We were already playing or observing this game;
3454                        no need to refetch history */
3455                     gameMode = IcsExamining;
3456                     if (pausing) {
3457                         pauseExamForwardMostMove = forwardMostMove;
3458                     } else if (currentMove < forwardMostMove) {
3459                         ForwardInner(forwardMostMove);
3460                     }
3461                 } else {
3462                     /* I don't think this case really can happen */
3463                     SendToICS(ics_prefix);
3464                     SendToICS("refresh\n");
3465                 }
3466                 continue;
3467             }
3468
3469             /* Error messages */
3470 //          if (ics_user_moved) {
3471             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3472                 if (looking_at(buf, &i, "Illegal move") ||
3473                     looking_at(buf, &i, "Not a legal move") ||
3474                     looking_at(buf, &i, "Your king is in check") ||
3475                     looking_at(buf, &i, "It isn't your turn") ||
3476                     looking_at(buf, &i, "It is not your move")) {
3477                     /* Illegal move */
3478                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3479                         currentMove = forwardMostMove-1;
3480                         DisplayMove(currentMove - 1); /* before DMError */
3481                         DrawPosition(FALSE, boards[currentMove]);
3482                         SwitchClocks(forwardMostMove-1); // [HGM] race
3483                         DisplayBothClocks();
3484                     }
3485                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3486                     ics_user_moved = 0;
3487                     continue;
3488                 }
3489             }
3490
3491             if (looking_at(buf, &i, "still have time") ||
3492                 looking_at(buf, &i, "not out of time") ||
3493                 looking_at(buf, &i, "either player is out of time") ||
3494                 looking_at(buf, &i, "has timeseal; checking")) {
3495                 /* We must have called his flag a little too soon */
3496                 whiteFlag = blackFlag = FALSE;
3497                 continue;
3498             }
3499
3500             if (looking_at(buf, &i, "added * seconds to") ||
3501                 looking_at(buf, &i, "seconds were added to")) {
3502                 /* Update the clocks */
3503                 SendToICS(ics_prefix);
3504                 SendToICS("refresh\n");
3505                 continue;
3506             }
3507
3508             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3509                 ics_clock_paused = TRUE;
3510                 StopClocks();
3511                 continue;
3512             }
3513
3514             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3515                 ics_clock_paused = FALSE;
3516                 StartClocks();
3517                 continue;
3518             }
3519
3520             /* Grab player ratings from the Creating: message.
3521                Note we have to check for the special case when
3522                the ICS inserts things like [white] or [black]. */
3523             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3524                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3525                 /* star_matches:
3526                    0    player 1 name (not necessarily white)
3527                    1    player 1 rating
3528                    2    empty, white, or black (IGNORED)
3529                    3    player 2 name (not necessarily black)
3530                    4    player 2 rating
3531
3532                    The names/ratings are sorted out when the game
3533                    actually starts (below).
3534                 */
3535                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3536                 player1Rating = string_to_rating(star_match[1]);
3537                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3538                 player2Rating = string_to_rating(star_match[4]);
3539
3540                 if (appData.debugMode)
3541                   fprintf(debugFP,
3542                           "Ratings from 'Creating:' %s %d, %s %d\n",
3543                           player1Name, player1Rating,
3544                           player2Name, player2Rating);
3545
3546                 continue;
3547             }
3548
3549             /* Improved generic start/end-of-game messages */
3550             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3551                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3552                 /* If tkind == 0: */
3553                 /* star_match[0] is the game number */
3554                 /*           [1] is the white player's name */
3555                 /*           [2] is the black player's name */
3556                 /* For end-of-game: */
3557                 /*           [3] is the reason for the game end */
3558                 /*           [4] is a PGN end game-token, preceded by " " */
3559                 /* For start-of-game: */
3560                 /*           [3] begins with "Creating" or "Continuing" */
3561                 /*           [4] is " *" or empty (don't care). */
3562                 int gamenum = atoi(star_match[0]);
3563                 char *whitename, *blackname, *why, *endtoken;
3564                 ChessMove endtype = EndOfFile;
3565
3566                 if (tkind == 0) {
3567                   whitename = star_match[1];
3568                   blackname = star_match[2];
3569                   why = star_match[3];
3570                   endtoken = star_match[4];
3571                 } else {
3572                   whitename = star_match[1];
3573                   blackname = star_match[3];
3574                   why = star_match[5];
3575                   endtoken = star_match[6];
3576                 }
3577
3578                 /* Game start messages */
3579                 if (strncmp(why, "Creating ", 9) == 0 ||
3580                     strncmp(why, "Continuing ", 11) == 0) {
3581                     gs_gamenum = gamenum;
3582                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3583                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3584 #if ZIPPY
3585                     if (appData.zippyPlay) {
3586                         ZippyGameStart(whitename, blackname);
3587                     }
3588 #endif /*ZIPPY*/
3589                     partnerBoardValid = FALSE; // [HGM] bughouse
3590                     continue;
3591                 }
3592
3593                 /* Game end messages */
3594                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3595                     ics_gamenum != gamenum) {
3596                     continue;
3597                 }
3598                 while (endtoken[0] == ' ') endtoken++;
3599                 switch (endtoken[0]) {
3600                   case '*':
3601                   default:
3602                     endtype = GameUnfinished;
3603                     break;
3604                   case '0':
3605                     endtype = BlackWins;
3606                     break;
3607                   case '1':
3608                     if (endtoken[1] == '/')
3609                       endtype = GameIsDrawn;
3610                     else
3611                       endtype = WhiteWins;
3612                     break;
3613                 }
3614                 GameEnds(endtype, why, GE_ICS);
3615 #if ZIPPY
3616                 if (appData.zippyPlay && first.initDone) {
3617                     ZippyGameEnd(endtype, why);
3618                     if (first.pr == NULL) {
3619                       /* Start the next process early so that we'll
3620                          be ready for the next challenge */
3621                       StartChessProgram(&first);
3622                     }
3623                     /* Send "new" early, in case this command takes
3624                        a long time to finish, so that we'll be ready
3625                        for the next challenge. */
3626                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3627                     Reset(TRUE, TRUE);
3628                 }
3629 #endif /*ZIPPY*/
3630                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3631                 continue;
3632             }
3633
3634             if (looking_at(buf, &i, "Removing game * from observation") ||
3635                 looking_at(buf, &i, "no longer observing game *") ||
3636                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3637                 if (gameMode == IcsObserving &&
3638                     atoi(star_match[0]) == ics_gamenum)
3639                   {
3640                       /* icsEngineAnalyze */
3641                       if (appData.icsEngineAnalyze) {
3642                             ExitAnalyzeMode();
3643                             ModeHighlight();
3644                       }
3645                       StopClocks();
3646                       gameMode = IcsIdle;
3647                       ics_gamenum = -1;
3648                       ics_user_moved = FALSE;
3649                   }
3650                 continue;
3651             }
3652
3653             if (looking_at(buf, &i, "no longer examining game *")) {
3654                 if (gameMode == IcsExamining &&
3655                     atoi(star_match[0]) == ics_gamenum)
3656                   {
3657                       gameMode = IcsIdle;
3658                       ics_gamenum = -1;
3659                       ics_user_moved = FALSE;
3660                   }
3661                 continue;
3662             }
3663
3664             /* Advance leftover_start past any newlines we find,
3665                so only partial lines can get reparsed */
3666             if (looking_at(buf, &i, "\n")) {
3667                 prevColor = curColor;
3668                 if (curColor != ColorNormal) {
3669                     if (oldi > next_out) {
3670                         SendToPlayer(&buf[next_out], oldi - next_out);
3671                         next_out = oldi;
3672                     }
3673                     Colorize(ColorNormal, FALSE);
3674                     curColor = ColorNormal;
3675                 }
3676                 if (started == STARTED_BOARD) {
3677                     started = STARTED_NONE;
3678                     parse[parse_pos] = NULLCHAR;
3679                     ParseBoard12(parse);
3680                     ics_user_moved = 0;
3681
3682                     /* Send premove here */
3683                     if (appData.premove) {
3684                       char str[MSG_SIZ];
3685                       if (currentMove == 0 &&
3686                           gameMode == IcsPlayingWhite &&
3687                           appData.premoveWhite) {
3688                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3689                         if (appData.debugMode)
3690                           fprintf(debugFP, "Sending premove:\n");
3691                         SendToICS(str);
3692                       } else if (currentMove == 1 &&
3693                                  gameMode == IcsPlayingBlack &&
3694                                  appData.premoveBlack) {
3695                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3696                         if (appData.debugMode)
3697                           fprintf(debugFP, "Sending premove:\n");
3698                         SendToICS(str);
3699                       } else if (gotPremove) {
3700                         gotPremove = 0;
3701                         ClearPremoveHighlights();
3702                         if (appData.debugMode)
3703                           fprintf(debugFP, "Sending premove:\n");
3704                           UserMoveEvent(premoveFromX, premoveFromY,
3705                                         premoveToX, premoveToY,
3706                                         premovePromoChar);
3707                       }
3708                     }
3709
3710                     /* Usually suppress following prompt */
3711                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3712                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3713                         if (looking_at(buf, &i, "*% ")) {
3714                             savingComment = FALSE;
3715                             suppressKibitz = 0;
3716                         }
3717                     }
3718                     next_out = i;
3719                 } else if (started == STARTED_HOLDINGS) {
3720                     int gamenum;
3721                     char new_piece[MSG_SIZ];
3722                     started = STARTED_NONE;
3723                     parse[parse_pos] = NULLCHAR;
3724                     if (appData.debugMode)
3725                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3726                                                         parse, currentMove);
3727                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3728                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3729                         if (gameInfo.variant == VariantNormal) {
3730                           /* [HGM] We seem to switch variant during a game!
3731                            * Presumably no holdings were displayed, so we have
3732                            * to move the position two files to the right to
3733                            * create room for them!
3734                            */
3735                           VariantClass newVariant;
3736                           switch(gameInfo.boardWidth) { // base guess on board width
3737                                 case 9:  newVariant = VariantShogi; break;
3738                                 case 10: newVariant = VariantGreat; break;
3739                                 default: newVariant = VariantCrazyhouse; break;
3740                           }
3741                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3742                           /* Get a move list just to see the header, which
3743                              will tell us whether this is really bug or zh */
3744                           if (ics_getting_history == H_FALSE) {
3745                             ics_getting_history = H_REQUESTED;
3746                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3747                             SendToICS(str);
3748                           }
3749                         }
3750                         new_piece[0] = NULLCHAR;
3751                         sscanf(parse, "game %d white [%s black [%s <- %s",
3752                                &gamenum, white_holding, black_holding,
3753                                new_piece);
3754                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3755                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3756                         /* [HGM] copy holdings to board holdings area */
3757                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3758                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3759                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3760 #if ZIPPY
3761                         if (appData.zippyPlay && first.initDone) {
3762                             ZippyHoldings(white_holding, black_holding,
3763                                           new_piece);
3764                         }
3765 #endif /*ZIPPY*/
3766                         if (tinyLayout || smallLayout) {
3767                             char wh[16], bh[16];
3768                             PackHolding(wh, white_holding);
3769                             PackHolding(bh, black_holding);
3770                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3771                                     gameInfo.white, gameInfo.black);
3772                         } else {
3773                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3774                                     gameInfo.white, white_holding,
3775                                     gameInfo.black, black_holding);
3776                         }
3777                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3778                         DrawPosition(FALSE, boards[currentMove]);
3779                         DisplayTitle(str);
3780                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3781                         sscanf(parse, "game %d white [%s black [%s <- %s",
3782                                &gamenum, white_holding, black_holding,
3783                                new_piece);
3784                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3785                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3786                         /* [HGM] copy holdings to partner-board holdings area */
3787                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3788                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3789                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3790                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3791                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3792                       }
3793                     }
3794                     /* Suppress following prompt */
3795                     if (looking_at(buf, &i, "*% ")) {
3796                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3797                         savingComment = FALSE;
3798                         suppressKibitz = 0;
3799                     }
3800                     next_out = i;
3801                 }
3802                 continue;
3803             }
3804
3805             i++;                /* skip unparsed character and loop back */
3806         }
3807
3808         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3809 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3810 //          SendToPlayer(&buf[next_out], i - next_out);
3811             started != STARTED_HOLDINGS && leftover_start > next_out) {
3812             SendToPlayer(&buf[next_out], leftover_start - next_out);
3813             next_out = i;
3814         }
3815
3816         leftover_len = buf_len - leftover_start;
3817         /* if buffer ends with something we couldn't parse,
3818            reparse it after appending the next read */
3819
3820     } else if (count == 0) {
3821         RemoveInputSource(isr);
3822         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3823     } else {
3824         DisplayFatalError(_("Error reading from ICS"), error, 1);
3825     }
3826 }
3827
3828
3829 /* Board style 12 looks like this:
3830
3831    <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
3832
3833  * The "<12> " is stripped before it gets to this routine.  The two
3834  * trailing 0's (flip state and clock ticking) are later addition, and
3835  * some chess servers may not have them, or may have only the first.
3836  * Additional trailing fields may be added in the future.
3837  */
3838
3839 #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"
3840
3841 #define RELATION_OBSERVING_PLAYED    0
3842 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3843 #define RELATION_PLAYING_MYMOVE      1
3844 #define RELATION_PLAYING_NOTMYMOVE  -1
3845 #define RELATION_EXAMINING           2
3846 #define RELATION_ISOLATED_BOARD     -3
3847 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3848
3849 void
3850 ParseBoard12(string)
3851      char *string;
3852 {
3853     GameMode newGameMode;
3854     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3855     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3856     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3857     char to_play, board_chars[200];
3858     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3859     char black[32], white[32];
3860     Board board;
3861     int prevMove = currentMove;
3862     int ticking = 2;
3863     ChessMove moveType;
3864     int fromX, fromY, toX, toY;
3865     char promoChar;
3866     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3867     char *bookHit = NULL; // [HGM] book
3868     Boolean weird = FALSE, reqFlag = FALSE;
3869
3870     fromX = fromY = toX = toY = -1;
3871
3872     newGame = FALSE;
3873
3874     if (appData.debugMode)
3875       fprintf(debugFP, _("Parsing board: %s\n"), string);
3876
3877     move_str[0] = NULLCHAR;
3878     elapsed_time[0] = NULLCHAR;
3879     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3880         int  i = 0, j;
3881         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3882             if(string[i] == ' ') { ranks++; files = 0; }
3883             else files++;
3884             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3885             i++;
3886         }
3887         for(j = 0; j <i; j++) board_chars[j] = string[j];
3888         board_chars[i] = '\0';
3889         string += i + 1;
3890     }
3891     n = sscanf(string, PATTERN, &to_play, &double_push,
3892                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3893                &gamenum, white, black, &relation, &basetime, &increment,
3894                &white_stren, &black_stren, &white_time, &black_time,
3895                &moveNum, str, elapsed_time, move_str, &ics_flip,
3896                &ticking);
3897
3898     if (n < 21) {
3899         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3900         DisplayError(str, 0);
3901         return;
3902     }
3903
3904     /* Convert the move number to internal form */
3905     moveNum = (moveNum - 1) * 2;
3906     if (to_play == 'B') moveNum++;
3907     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3908       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3909                         0, 1);
3910       return;
3911     }
3912
3913     switch (relation) {
3914       case RELATION_OBSERVING_PLAYED:
3915       case RELATION_OBSERVING_STATIC:
3916         if (gamenum == -1) {
3917             /* Old ICC buglet */
3918             relation = RELATION_OBSERVING_STATIC;
3919         }
3920         newGameMode = IcsObserving;
3921         break;
3922       case RELATION_PLAYING_MYMOVE:
3923       case RELATION_PLAYING_NOTMYMOVE:
3924         newGameMode =
3925           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3926             IcsPlayingWhite : IcsPlayingBlack;
3927         break;
3928       case RELATION_EXAMINING:
3929         newGameMode = IcsExamining;
3930         break;
3931       case RELATION_ISOLATED_BOARD:
3932       default:
3933         /* Just display this board.  If user was doing something else,
3934            we will forget about it until the next board comes. */
3935         newGameMode = IcsIdle;
3936         break;
3937       case RELATION_STARTING_POSITION:
3938         newGameMode = gameMode;
3939         break;
3940     }
3941
3942     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3943          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3944       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3945       char *toSqr;
3946       for (k = 0; k < ranks; k++) {
3947         for (j = 0; j < files; j++)
3948           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3949         if(gameInfo.holdingsWidth > 1) {
3950              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3951              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3952         }
3953       }
3954       CopyBoard(partnerBoard, board);
3955       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3956         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3957         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3958       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3959       if(toSqr = strchr(str, '-')) {
3960         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3961         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3962       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3963       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3964       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3965       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3966       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3967       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3968                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3969       DisplayMessage(partnerStatus, "");
3970         partnerBoardValid = TRUE;
3971       return;
3972     }
3973
3974     /* Modify behavior for initial board display on move listing
3975        of wild games.
3976        */
3977     switch (ics_getting_history) {
3978       case H_FALSE:
3979       case H_REQUESTED:
3980         break;
3981       case H_GOT_REQ_HEADER:
3982       case H_GOT_UNREQ_HEADER:
3983         /* This is the initial position of the current game */
3984         gamenum = ics_gamenum;
3985         moveNum = 0;            /* old ICS bug workaround */
3986         if (to_play == 'B') {
3987           startedFromSetupPosition = TRUE;
3988           blackPlaysFirst = TRUE;
3989           moveNum = 1;
3990           if (forwardMostMove == 0) forwardMostMove = 1;
3991           if (backwardMostMove == 0) backwardMostMove = 1;
3992           if (currentMove == 0) currentMove = 1;
3993         }
3994         newGameMode = gameMode;
3995         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3996         break;
3997       case H_GOT_UNWANTED_HEADER:
3998         /* This is an initial board that we don't want */
3999         return;
4000       case H_GETTING_MOVES:
4001         /* Should not happen */
4002         DisplayError(_("Error gathering move list: extra board"), 0);
4003         ics_getting_history = H_FALSE;
4004         return;
4005     }
4006
4007    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4008                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4009      /* [HGM] We seem to have switched variant unexpectedly
4010       * Try to guess new variant from board size
4011       */
4012           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4013           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4014           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4015           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4016           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4017           if(!weird) newVariant = VariantNormal;
4018           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4019           /* Get a move list just to see the header, which
4020              will tell us whether this is really bug or zh */
4021           if (ics_getting_history == H_FALSE) {
4022             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4023             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4024             SendToICS(str);
4025           }
4026     }
4027
4028     /* Take action if this is the first board of a new game, or of a
4029        different game than is currently being displayed.  */
4030     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4031         relation == RELATION_ISOLATED_BOARD) {
4032
4033         /* Forget the old game and get the history (if any) of the new one */
4034         if (gameMode != BeginningOfGame) {
4035           Reset(TRUE, TRUE);
4036         }
4037         newGame = TRUE;
4038         if (appData.autoRaiseBoard) BoardToTop();
4039         prevMove = -3;
4040         if (gamenum == -1) {
4041             newGameMode = IcsIdle;
4042         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4043                    appData.getMoveList && !reqFlag) {
4044             /* Need to get game history */
4045             ics_getting_history = H_REQUESTED;
4046             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4047             SendToICS(str);
4048         }
4049
4050         /* Initially flip the board to have black on the bottom if playing
4051            black or if the ICS flip flag is set, but let the user change
4052            it with the Flip View button. */
4053         flipView = appData.autoFlipView ?
4054           (newGameMode == IcsPlayingBlack) || ics_flip :
4055           appData.flipView;
4056
4057         /* Done with values from previous mode; copy in new ones */
4058         gameMode = newGameMode;
4059         ModeHighlight();
4060         ics_gamenum = gamenum;
4061         if (gamenum == gs_gamenum) {
4062             int klen = strlen(gs_kind);
4063             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4064             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4065             gameInfo.event = StrSave(str);
4066         } else {
4067             gameInfo.event = StrSave("ICS game");
4068         }
4069         gameInfo.site = StrSave(appData.icsHost);
4070         gameInfo.date = PGNDate();
4071         gameInfo.round = StrSave("-");
4072         gameInfo.white = StrSave(white);
4073         gameInfo.black = StrSave(black);
4074         timeControl = basetime * 60 * 1000;
4075         timeControl_2 = 0;
4076         timeIncrement = increment * 1000;
4077         movesPerSession = 0;
4078         gameInfo.timeControl = TimeControlTagValue();
4079         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4080   if (appData.debugMode) {
4081     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4082     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4083     setbuf(debugFP, NULL);
4084   }
4085
4086         gameInfo.outOfBook = NULL;
4087
4088         /* Do we have the ratings? */
4089         if (strcmp(player1Name, white) == 0 &&
4090             strcmp(player2Name, black) == 0) {
4091             if (appData.debugMode)
4092               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4093                       player1Rating, player2Rating);
4094             gameInfo.whiteRating = player1Rating;
4095             gameInfo.blackRating = player2Rating;
4096         } else if (strcmp(player2Name, white) == 0 &&
4097                    strcmp(player1Name, black) == 0) {
4098             if (appData.debugMode)
4099               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4100                       player2Rating, player1Rating);
4101             gameInfo.whiteRating = player2Rating;
4102             gameInfo.blackRating = player1Rating;
4103         }
4104         player1Name[0] = player2Name[0] = NULLCHAR;
4105
4106         /* Silence shouts if requested */
4107         if (appData.quietPlay &&
4108             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4109             SendToICS(ics_prefix);
4110             SendToICS("set shout 0\n");
4111         }
4112     }
4113
4114     /* Deal with midgame name changes */
4115     if (!newGame) {
4116         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4117             if (gameInfo.white) free(gameInfo.white);
4118             gameInfo.white = StrSave(white);
4119         }
4120         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4121             if (gameInfo.black) free(gameInfo.black);
4122             gameInfo.black = StrSave(black);
4123         }
4124     }
4125
4126     /* Throw away game result if anything actually changes in examine mode */
4127     if (gameMode == IcsExamining && !newGame) {
4128         gameInfo.result = GameUnfinished;
4129         if (gameInfo.resultDetails != NULL) {
4130             free(gameInfo.resultDetails);
4131             gameInfo.resultDetails = NULL;
4132         }
4133     }
4134
4135     /* In pausing && IcsExamining mode, we ignore boards coming
4136        in if they are in a different variation than we are. */
4137     if (pauseExamInvalid) return;
4138     if (pausing && gameMode == IcsExamining) {
4139         if (moveNum <= pauseExamForwardMostMove) {
4140             pauseExamInvalid = TRUE;
4141             forwardMostMove = pauseExamForwardMostMove;
4142             return;
4143         }
4144     }
4145
4146   if (appData.debugMode) {
4147     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4148   }
4149     /* Parse the board */
4150     for (k = 0; k < ranks; k++) {
4151       for (j = 0; j < files; j++)
4152         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4153       if(gameInfo.holdingsWidth > 1) {
4154            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4155            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4156       }
4157     }
4158     CopyBoard(boards[moveNum], board);
4159     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4160     if (moveNum == 0) {
4161         startedFromSetupPosition =
4162           !CompareBoards(board, initialPosition);
4163         if(startedFromSetupPosition)
4164             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4165     }
4166
4167     /* [HGM] Set castling rights. Take the outermost Rooks,
4168        to make it also work for FRC opening positions. Note that board12
4169        is really defective for later FRC positions, as it has no way to
4170        indicate which Rook can castle if they are on the same side of King.
4171        For the initial position we grant rights to the outermost Rooks,
4172        and remember thos rights, and we then copy them on positions
4173        later in an FRC game. This means WB might not recognize castlings with
4174        Rooks that have moved back to their original position as illegal,
4175        but in ICS mode that is not its job anyway.
4176     */
4177     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4178     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4179
4180         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4181             if(board[0][i] == WhiteRook) j = i;
4182         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4183         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4184             if(board[0][i] == WhiteRook) j = i;
4185         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4186         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4187             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4188         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4189         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4190             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4191         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4192
4193         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4194         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4195             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4196         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4197             if(board[BOARD_HEIGHT-1][k] == bKing)
4198                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4199         if(gameInfo.variant == VariantTwoKings) {
4200             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4201             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4202             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4203         }
4204     } else { int r;
4205         r = boards[moveNum][CASTLING][0] = initialRights[0];
4206         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4207         r = boards[moveNum][CASTLING][1] = initialRights[1];
4208         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4209         r = boards[moveNum][CASTLING][3] = initialRights[3];
4210         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4211         r = boards[moveNum][CASTLING][4] = initialRights[4];
4212         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4213         /* wildcastle kludge: always assume King has rights */
4214         r = boards[moveNum][CASTLING][2] = initialRights[2];
4215         r = boards[moveNum][CASTLING][5] = initialRights[5];
4216     }
4217     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4218     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4219
4220
4221     if (ics_getting_history == H_GOT_REQ_HEADER ||
4222         ics_getting_history == H_GOT_UNREQ_HEADER) {
4223         /* This was an initial position from a move list, not
4224            the current position */
4225         return;
4226     }
4227
4228     /* Update currentMove and known move number limits */
4229     newMove = newGame || moveNum > forwardMostMove;
4230
4231     if (newGame) {
4232         forwardMostMove = backwardMostMove = currentMove = moveNum;
4233         if (gameMode == IcsExamining && moveNum == 0) {
4234           /* Workaround for ICS limitation: we are not told the wild
4235              type when starting to examine a game.  But if we ask for
4236              the move list, the move list header will tell us */
4237             ics_getting_history = H_REQUESTED;
4238             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4239             SendToICS(str);
4240         }
4241     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4242                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4243 #if ZIPPY
4244         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4245         /* [HGM] applied this also to an engine that is silently watching        */
4246         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4247             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4248             gameInfo.variant == currentlyInitializedVariant) {
4249           takeback = forwardMostMove - moveNum;
4250           for (i = 0; i < takeback; i++) {
4251             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4252             SendToProgram("undo\n", &first);
4253           }
4254         }
4255 #endif
4256
4257         forwardMostMove = moveNum;
4258         if (!pausing || currentMove > forwardMostMove)
4259           currentMove = forwardMostMove;
4260     } else {
4261         /* New part of history that is not contiguous with old part */
4262         if (pausing && gameMode == IcsExamining) {
4263             pauseExamInvalid = TRUE;
4264             forwardMostMove = pauseExamForwardMostMove;
4265             return;
4266         }
4267         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4268 #if ZIPPY
4269             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4270                 // [HGM] when we will receive the move list we now request, it will be
4271                 // fed to the engine from the first move on. So if the engine is not
4272                 // in the initial position now, bring it there.
4273                 InitChessProgram(&first, 0);
4274             }
4275 #endif
4276             ics_getting_history = H_REQUESTED;
4277             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4278             SendToICS(str);
4279         }
4280         forwardMostMove = backwardMostMove = currentMove = moveNum;
4281     }
4282
4283     /* Update the clocks */
4284     if (strchr(elapsed_time, '.')) {
4285       /* Time is in ms */
4286       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4287       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4288     } else {
4289       /* Time is in seconds */
4290       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4291       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4292     }
4293
4294
4295 #if ZIPPY
4296     if (appData.zippyPlay && newGame &&
4297         gameMode != IcsObserving && gameMode != IcsIdle &&
4298         gameMode != IcsExamining)
4299       ZippyFirstBoard(moveNum, basetime, increment);
4300 #endif
4301
4302     /* Put the move on the move list, first converting
4303        to canonical algebraic form. */
4304     if (moveNum > 0) {
4305   if (appData.debugMode) {
4306     if (appData.debugMode) { int f = forwardMostMove;
4307         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4308                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4309                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4310     }
4311     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4312     fprintf(debugFP, "moveNum = %d\n", moveNum);
4313     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4314     setbuf(debugFP, NULL);
4315   }
4316         if (moveNum <= backwardMostMove) {
4317             /* We don't know what the board looked like before
4318                this move.  Punt. */
4319           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4320             strcat(parseList[moveNum - 1], " ");
4321             strcat(parseList[moveNum - 1], elapsed_time);
4322             moveList[moveNum - 1][0] = NULLCHAR;
4323         } else if (strcmp(move_str, "none") == 0) {
4324             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4325             /* Again, we don't know what the board looked like;
4326                this is really the start of the game. */
4327             parseList[moveNum - 1][0] = NULLCHAR;
4328             moveList[moveNum - 1][0] = NULLCHAR;
4329             backwardMostMove = moveNum;
4330             startedFromSetupPosition = TRUE;
4331             fromX = fromY = toX = toY = -1;
4332         } else {
4333           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4334           //                 So we parse the long-algebraic move string in stead of the SAN move
4335           int valid; char buf[MSG_SIZ], *prom;
4336
4337           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4338                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4339           // str looks something like "Q/a1-a2"; kill the slash
4340           if(str[1] == '/')
4341             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4342           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4343           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4344                 strcat(buf, prom); // long move lacks promo specification!
4345           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4346                 if(appData.debugMode)
4347                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4348                 safeStrCpy(move_str, buf, MSG_SIZ);
4349           }
4350           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4351                                 &fromX, &fromY, &toX, &toY, &promoChar)
4352                || ParseOneMove(buf, moveNum - 1, &moveType,
4353                                 &fromX, &fromY, &toX, &toY, &promoChar);
4354           // end of long SAN patch
4355           if (valid) {
4356             (void) CoordsToAlgebraic(boards[moveNum - 1],
4357                                      PosFlags(moveNum - 1),
4358                                      fromY, fromX, toY, toX, promoChar,
4359                                      parseList[moveNum-1]);
4360             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4361               case MT_NONE:
4362               case MT_STALEMATE:
4363               default:
4364                 break;
4365               case MT_CHECK:
4366                 if(gameInfo.variant != VariantShogi)
4367                     strcat(parseList[moveNum - 1], "+");
4368                 break;
4369               case MT_CHECKMATE:
4370               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4371                 strcat(parseList[moveNum - 1], "#");
4372                 break;
4373             }
4374             strcat(parseList[moveNum - 1], " ");
4375             strcat(parseList[moveNum - 1], elapsed_time);
4376             /* currentMoveString is set as a side-effect of ParseOneMove */
4377             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4378             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4379             strcat(moveList[moveNum - 1], "\n");
4380
4381             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4382               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4383                 ChessSquare old, new = boards[moveNum][k][j];
4384                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4385                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4386                   if(old == new) continue;
4387                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4388                   else if(new == WhiteWazir || new == BlackWazir) {
4389                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4390                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4391                       else boards[moveNum][k][j] = old; // preserve type of Gold
4392                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4393                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4394               }
4395           } else {
4396             /* Move from ICS was illegal!?  Punt. */
4397             if (appData.debugMode) {
4398               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4399               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4400             }
4401             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4402             strcat(parseList[moveNum - 1], " ");
4403             strcat(parseList[moveNum - 1], elapsed_time);
4404             moveList[moveNum - 1][0] = NULLCHAR;
4405             fromX = fromY = toX = toY = -1;
4406           }
4407         }
4408   if (appData.debugMode) {
4409     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4410     setbuf(debugFP, NULL);
4411   }
4412
4413 #if ZIPPY
4414         /* Send move to chess program (BEFORE animating it). */
4415         if (appData.zippyPlay && !newGame && newMove &&
4416            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4417
4418             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4419                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4420                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4421                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4422                             move_str);
4423                     DisplayError(str, 0);
4424                 } else {
4425                     if (first.sendTime) {
4426                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4427                     }
4428                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4429                     if (firstMove && !bookHit) {
4430                         firstMove = FALSE;
4431                         if (first.useColors) {
4432                           SendToProgram(gameMode == IcsPlayingWhite ?
4433                                         "white\ngo\n" :
4434                                         "black\ngo\n", &first);
4435                         } else {
4436                           SendToProgram("go\n", &first);
4437                         }
4438                         first.maybeThinking = TRUE;
4439                     }
4440                 }
4441             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4442               if (moveList[moveNum - 1][0] == NULLCHAR) {
4443                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4444                 DisplayError(str, 0);
4445               } else {
4446                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4447                 SendMoveToProgram(moveNum - 1, &first);
4448               }
4449             }
4450         }
4451 #endif
4452     }
4453
4454     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4455         /* If move comes from a remote source, animate it.  If it
4456            isn't remote, it will have already been animated. */
4457         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4458             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4459         }
4460         if (!pausing && appData.highlightLastMove) {
4461             SetHighlights(fromX, fromY, toX, toY);
4462         }
4463     }
4464
4465     /* Start the clocks */
4466     whiteFlag = blackFlag = FALSE;
4467     appData.clockMode = !(basetime == 0 && increment == 0);
4468     if (ticking == 0) {
4469       ics_clock_paused = TRUE;
4470       StopClocks();
4471     } else if (ticking == 1) {
4472       ics_clock_paused = FALSE;
4473     }
4474     if (gameMode == IcsIdle ||
4475         relation == RELATION_OBSERVING_STATIC ||
4476         relation == RELATION_EXAMINING ||
4477         ics_clock_paused)
4478       DisplayBothClocks();
4479     else
4480       StartClocks();
4481
4482     /* Display opponents and material strengths */
4483     if (gameInfo.variant != VariantBughouse &&
4484         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4485         if (tinyLayout || smallLayout) {
4486             if(gameInfo.variant == VariantNormal)
4487               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4488                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4489                     basetime, increment);
4490             else
4491               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4492                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4493                     basetime, increment, (int) gameInfo.variant);
4494         } else {
4495             if(gameInfo.variant == VariantNormal)
4496               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4497                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4498                     basetime, increment);
4499             else
4500               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4501                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4502                     basetime, increment, VariantName(gameInfo.variant));
4503         }
4504         DisplayTitle(str);
4505   if (appData.debugMode) {
4506     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4507   }
4508     }
4509
4510
4511     /* Display the board */
4512     if (!pausing && !appData.noGUI) {
4513
4514       if (appData.premove)
4515           if (!gotPremove ||
4516              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4517              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4518               ClearPremoveHighlights();
4519
4520       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4521         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4522       DrawPosition(j, boards[currentMove]);
4523
4524       DisplayMove(moveNum - 1);
4525       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4526             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4527               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4528         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4529       }
4530     }
4531
4532     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4533 #if ZIPPY
4534     if(bookHit) { // [HGM] book: simulate book reply
4535         static char bookMove[MSG_SIZ]; // a bit generous?
4536
4537         programStats.nodes = programStats.depth = programStats.time =
4538         programStats.score = programStats.got_only_move = 0;
4539         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4540
4541         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4542         strcat(bookMove, bookHit);
4543         HandleMachineMove(bookMove, &first);
4544     }
4545 #endif
4546 }
4547
4548 void
4549 GetMoveListEvent()
4550 {
4551     char buf[MSG_SIZ];
4552     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4553         ics_getting_history = H_REQUESTED;
4554         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4555         SendToICS(buf);
4556     }
4557 }
4558
4559 void
4560 AnalysisPeriodicEvent(force)
4561      int force;
4562 {
4563     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4564          && !force) || !appData.periodicUpdates)
4565       return;
4566
4567     /* Send . command to Crafty to collect stats */
4568     SendToProgram(".\n", &first);
4569
4570     /* Don't send another until we get a response (this makes
4571        us stop sending to old Crafty's which don't understand
4572        the "." command (sending illegal cmds resets node count & time,
4573        which looks bad)) */
4574     programStats.ok_to_send = 0;
4575 }
4576
4577 void ics_update_width(new_width)
4578         int new_width;
4579 {
4580         ics_printf("set width %d\n", new_width);
4581 }
4582
4583 void
4584 SendMoveToProgram(moveNum, cps)
4585      int moveNum;
4586      ChessProgramState *cps;
4587 {
4588     char buf[MSG_SIZ];
4589
4590     if (cps->useUsermove) {
4591       SendToProgram("usermove ", cps);
4592     }
4593     if (cps->useSAN) {
4594       char *space;
4595       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4596         int len = space - parseList[moveNum];
4597         memcpy(buf, parseList[moveNum], len);
4598         buf[len++] = '\n';
4599         buf[len] = NULLCHAR;
4600       } else {
4601         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4602       }
4603       SendToProgram(buf, cps);
4604     } else {
4605       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4606         AlphaRank(moveList[moveNum], 4);
4607         SendToProgram(moveList[moveNum], cps);
4608         AlphaRank(moveList[moveNum], 4); // and back
4609       } else
4610       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4611        * the engine. It would be nice to have a better way to identify castle
4612        * moves here. */
4613       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4614                                                                          && cps->useOOCastle) {
4615         int fromX = moveList[moveNum][0] - AAA;
4616         int fromY = moveList[moveNum][1] - ONE;
4617         int toX = moveList[moveNum][2] - AAA;
4618         int toY = moveList[moveNum][3] - ONE;
4619         if((boards[moveNum][fromY][fromX] == WhiteKing
4620             && boards[moveNum][toY][toX] == WhiteRook)
4621            || (boards[moveNum][fromY][fromX] == BlackKing
4622                && boards[moveNum][toY][toX] == BlackRook)) {
4623           if(toX > fromX) SendToProgram("O-O\n", cps);
4624           else SendToProgram("O-O-O\n", cps);
4625         }
4626         else SendToProgram(moveList[moveNum], cps);
4627       }
4628       else SendToProgram(moveList[moveNum], cps);
4629       /* End of additions by Tord */
4630     }
4631
4632     /* [HGM] setting up the opening has brought engine in force mode! */
4633     /*       Send 'go' if we are in a mode where machine should play. */
4634     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4635         (gameMode == TwoMachinesPlay   ||
4636 #if ZIPPY
4637          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4638 #endif
4639          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4640         SendToProgram("go\n", cps);
4641   if (appData.debugMode) {
4642     fprintf(debugFP, "(extra)\n");
4643   }
4644     }
4645     setboardSpoiledMachineBlack = 0;
4646 }
4647
4648 void
4649 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4650      ChessMove moveType;
4651      int fromX, fromY, toX, toY;
4652      char promoChar;
4653 {
4654     char user_move[MSG_SIZ];
4655
4656     switch (moveType) {
4657       default:
4658         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4659                 (int)moveType, fromX, fromY, toX, toY);
4660         DisplayError(user_move + strlen("say "), 0);
4661         break;
4662       case WhiteKingSideCastle:
4663       case BlackKingSideCastle:
4664       case WhiteQueenSideCastleWild:
4665       case BlackQueenSideCastleWild:
4666       /* PUSH Fabien */
4667       case WhiteHSideCastleFR:
4668       case BlackHSideCastleFR:
4669       /* POP Fabien */
4670         snprintf(user_move, MSG_SIZ, "o-o\n");
4671         break;
4672       case WhiteQueenSideCastle:
4673       case BlackQueenSideCastle:
4674       case WhiteKingSideCastleWild:
4675       case BlackKingSideCastleWild:
4676       /* PUSH Fabien */
4677       case WhiteASideCastleFR:
4678       case BlackASideCastleFR:
4679       /* POP Fabien */
4680         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4681         break;
4682       case WhiteNonPromotion:
4683       case BlackNonPromotion:
4684         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4685         break;
4686       case WhitePromotion:
4687       case BlackPromotion:
4688         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4689           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4690                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4691                 PieceToChar(WhiteFerz));
4692         else if(gameInfo.variant == VariantGreat)
4693           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4694                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4695                 PieceToChar(WhiteMan));
4696         else
4697           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4698                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4699                 promoChar);
4700         break;
4701       case WhiteDrop:
4702       case BlackDrop:
4703       drop:
4704         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4705                  ToUpper(PieceToChar((ChessSquare) fromX)),
4706                  AAA + toX, ONE + toY);
4707         break;
4708       case IllegalMove:  /* could be a variant we don't quite understand */
4709         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4710       case NormalMove:
4711       case WhiteCapturesEnPassant:
4712       case BlackCapturesEnPassant:
4713         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4714                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4715         break;
4716     }
4717     SendToICS(user_move);
4718     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4719         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4720 }
4721
4722 void
4723 UploadGameEvent()
4724 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4725     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4726     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4727     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4728         DisplayError("You cannot do this while you are playing or observing", 0);
4729         return;
4730     }
4731     if(gameMode != IcsExamining) { // is this ever not the case?
4732         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4733
4734         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4735           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4736         } else { // on FICS we must first go to general examine mode
4737           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4738         }
4739         if(gameInfo.variant != VariantNormal) {
4740             // try figure out wild number, as xboard names are not always valid on ICS
4741             for(i=1; i<=36; i++) {
4742               snprintf(buf, MSG_SIZ, "wild/%d", i);
4743                 if(StringToVariant(buf) == gameInfo.variant) break;
4744             }
4745             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4746             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4747             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4748         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4749         SendToICS(ics_prefix);
4750         SendToICS(buf);
4751         if(startedFromSetupPosition || backwardMostMove != 0) {
4752           fen = PositionToFEN(backwardMostMove, NULL);
4753           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4754             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4755             SendToICS(buf);
4756           } else { // FICS: everything has to set by separate bsetup commands
4757             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4758             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4759             SendToICS(buf);
4760             if(!WhiteOnMove(backwardMostMove)) {
4761                 SendToICS("bsetup tomove black\n");
4762             }
4763             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4764             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4765             SendToICS(buf);
4766             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4767             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4768             SendToICS(buf);
4769             i = boards[backwardMostMove][EP_STATUS];
4770             if(i >= 0) { // set e.p.
4771               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4772                 SendToICS(buf);
4773             }
4774             bsetup++;
4775           }
4776         }
4777       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4778             SendToICS("bsetup done\n"); // switch to normal examining.
4779     }
4780     for(i = backwardMostMove; i<last; i++) {
4781         char buf[20];
4782         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4783         SendToICS(buf);
4784     }
4785     SendToICS(ics_prefix);
4786     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4787 }
4788
4789 void
4790 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4791      int rf, ff, rt, ft;
4792      char promoChar;
4793      char move[7];
4794 {
4795     if (rf == DROP_RANK) {
4796       sprintf(move, "%c@%c%c\n",
4797                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4798     } else {
4799         if (promoChar == 'x' || promoChar == NULLCHAR) {
4800           sprintf(move, "%c%c%c%c\n",
4801                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4802         } else {
4803             sprintf(move, "%c%c%c%c%c\n",
4804                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4805         }
4806     }
4807 }
4808
4809 void
4810 ProcessICSInitScript(f)
4811      FILE *f;
4812 {
4813     char buf[MSG_SIZ];
4814
4815     while (fgets(buf, MSG_SIZ, f)) {
4816         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4817     }
4818
4819     fclose(f);
4820 }
4821
4822
4823 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4824 void
4825 AlphaRank(char *move, int n)
4826 {
4827 //    char *p = move, c; int x, y;
4828
4829     if (appData.debugMode) {
4830         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4831     }
4832
4833     if(move[1]=='*' &&
4834        move[2]>='0' && move[2]<='9' &&
4835        move[3]>='a' && move[3]<='x'    ) {
4836         move[1] = '@';
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[0]>='0' && move[0]<='9' &&
4841        move[1]>='a' && move[1]<='x' &&
4842        move[2]>='0' && move[2]<='9' &&
4843        move[3]>='a' && move[3]<='x'    ) {
4844         /* input move, Shogi -> normal */
4845         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4846         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4847         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4848         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4849     } else
4850     if(move[1]=='@' &&
4851        move[3]>='0' && move[3]<='9' &&
4852        move[2]>='a' && move[2]<='x'    ) {
4853         move[1] = '*';
4854         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4855         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4856     } else
4857     if(
4858        move[0]>='a' && move[0]<='x' &&
4859        move[3]>='0' && move[3]<='9' &&
4860        move[2]>='a' && move[2]<='x'    ) {
4861          /* output move, normal -> Shogi */
4862         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4863         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4864         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4865         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4866         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4867     }
4868     if (appData.debugMode) {
4869         fprintf(debugFP, "   out = '%s'\n", move);
4870     }
4871 }
4872
4873 char yy_textstr[8000];
4874
4875 /* Parser for moves from gnuchess, ICS, or user typein box */
4876 Boolean
4877 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4878      char *move;
4879      int moveNum;
4880      ChessMove *moveType;
4881      int *fromX, *fromY, *toX, *toY;
4882      char *promoChar;
4883 {
4884     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4885
4886     switch (*moveType) {
4887       case WhitePromotion:
4888       case BlackPromotion:
4889       case WhiteNonPromotion:
4890       case BlackNonPromotion:
4891       case NormalMove:
4892       case WhiteCapturesEnPassant:
4893       case BlackCapturesEnPassant:
4894       case WhiteKingSideCastle:
4895       case WhiteQueenSideCastle:
4896       case BlackKingSideCastle:
4897       case BlackQueenSideCastle:
4898       case WhiteKingSideCastleWild:
4899       case WhiteQueenSideCastleWild:
4900       case BlackKingSideCastleWild:
4901       case BlackQueenSideCastleWild:
4902       /* Code added by Tord: */
4903       case WhiteHSideCastleFR:
4904       case WhiteASideCastleFR:
4905       case BlackHSideCastleFR:
4906       case BlackASideCastleFR:
4907       /* End of code added by Tord */
4908       case IllegalMove:         /* bug or odd chess variant */
4909         *fromX = currentMoveString[0] - AAA;
4910         *fromY = currentMoveString[1] - ONE;
4911         *toX = currentMoveString[2] - AAA;
4912         *toY = currentMoveString[3] - ONE;
4913         *promoChar = currentMoveString[4];
4914         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4915             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4916     if (appData.debugMode) {
4917         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4918     }
4919             *fromX = *fromY = *toX = *toY = 0;
4920             return FALSE;
4921         }
4922         if (appData.testLegality) {
4923           return (*moveType != IllegalMove);
4924         } else {
4925           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4926                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4927         }
4928
4929       case WhiteDrop:
4930       case BlackDrop:
4931         *fromX = *moveType == WhiteDrop ?
4932           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4933           (int) CharToPiece(ToLower(currentMoveString[0]));
4934         *fromY = DROP_RANK;
4935         *toX = currentMoveString[2] - AAA;
4936         *toY = currentMoveString[3] - ONE;
4937         *promoChar = NULLCHAR;
4938         return TRUE;
4939
4940       case AmbiguousMove:
4941       case ImpossibleMove:
4942       case EndOfFile:
4943       case ElapsedTime:
4944       case Comment:
4945       case PGNTag:
4946       case NAG:
4947       case WhiteWins:
4948       case BlackWins:
4949       case GameIsDrawn:
4950       default:
4951     if (appData.debugMode) {
4952         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4953     }
4954         /* bug? */
4955         *fromX = *fromY = *toX = *toY = 0;
4956         *promoChar = NULLCHAR;
4957         return FALSE;
4958     }
4959 }
4960
4961
4962 void
4963 ParsePV(char *pv, Boolean storeComments)
4964 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4965   int fromX, fromY, toX, toY; char promoChar;
4966   ChessMove moveType;
4967   Boolean valid;
4968   int nr = 0;
4969
4970   endPV = forwardMostMove;
4971   do {
4972     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4973     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4974     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4975 if(appData.debugMode){
4976 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);
4977 }
4978     if(!valid && nr == 0 &&
4979        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4980         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4981         // Hande case where played move is different from leading PV move
4982         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4983         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4984         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4985         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4986           endPV += 2; // if position different, keep this
4987           moveList[endPV-1][0] = fromX + AAA;
4988           moveList[endPV-1][1] = fromY + ONE;
4989           moveList[endPV-1][2] = toX + AAA;
4990           moveList[endPV-1][3] = toY + ONE;
4991           parseList[endPV-1][0] = NULLCHAR;
4992           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4993         }
4994       }
4995     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4996     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4997     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4998     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4999         valid++; // allow comments in PV
5000         continue;
5001     }
5002     nr++;
5003     if(endPV+1 > framePtr) break; // no space, truncate
5004     if(!valid) break;
5005     endPV++;
5006     CopyBoard(boards[endPV], boards[endPV-1]);
5007     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5008     moveList[endPV-1][0] = fromX + AAA;
5009     moveList[endPV-1][1] = fromY + ONE;
5010     moveList[endPV-1][2] = toX + AAA;
5011     moveList[endPV-1][3] = toY + ONE;
5012     if(storeComments)
5013         CoordsToAlgebraic(boards[endPV - 1],
5014                              PosFlags(endPV - 1),
5015                              fromY, fromX, toY, toX, promoChar,
5016                              parseList[endPV - 1]);
5017     else
5018         parseList[endPV-1][0] = NULLCHAR;
5019   } while(valid);
5020   currentMove = endPV;
5021   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5022   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5023                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5024   DrawPosition(TRUE, boards[currentMove]);
5025 }
5026
5027 static int lastX, lastY;
5028
5029 Boolean
5030 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5031 {
5032         int startPV;
5033         char *p;
5034
5035         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5036         lastX = x; lastY = y;
5037         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5038         startPV = index;
5039         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5040         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5041         index = startPV;
5042         do{ while(buf[index] && buf[index] != '\n') index++;
5043         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5044         buf[index] = 0;
5045         ParsePV(buf+startPV, FALSE);
5046         *start = startPV; *end = index-1;
5047         return TRUE;
5048 }
5049
5050 Boolean
5051 LoadPV(int x, int y)
5052 { // called on right mouse click to load PV
5053   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5054   lastX = x; lastY = y;
5055   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5056   return TRUE;
5057 }
5058
5059 void
5060 UnLoadPV()
5061 {
5062   if(endPV < 0) return;
5063   endPV = -1;
5064   currentMove = forwardMostMove;
5065   ClearPremoveHighlights();
5066   DrawPosition(TRUE, boards[currentMove]);
5067 }
5068
5069 void
5070 MovePV(int x, int y, int h)
5071 { // step through PV based on mouse coordinates (called on mouse move)
5072   int margin = h>>3, step = 0;
5073
5074   if(endPV < 0) return;
5075   // we must somehow check if right button is still down (might be released off board!)
5076   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5077   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5078   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5079   if(!step) return;
5080   lastX = x; lastY = y;
5081   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5082   currentMove += step;
5083   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5084   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5085                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5086   DrawPosition(FALSE, boards[currentMove]);
5087 }
5088
5089
5090 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5091 // All positions will have equal probability, but the current method will not provide a unique
5092 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5093 #define DARK 1
5094 #define LITE 2
5095 #define ANY 3
5096
5097 int squaresLeft[4];
5098 int piecesLeft[(int)BlackPawn];
5099 int seed, nrOfShuffles;
5100
5101 void GetPositionNumber()
5102 {       // sets global variable seed
5103         int i;
5104
5105         seed = appData.defaultFrcPosition;
5106         if(seed < 0) { // randomize based on time for negative FRC position numbers
5107                 for(i=0; i<50; i++) seed += random();
5108                 seed = random() ^ random() >> 8 ^ random() << 8;
5109                 if(seed<0) seed = -seed;
5110         }
5111 }
5112
5113 int put(Board board, int pieceType, int rank, int n, int shade)
5114 // put the piece on the (n-1)-th empty squares of the given shade
5115 {
5116         int i;
5117
5118         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5119                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5120                         board[rank][i] = (ChessSquare) pieceType;
5121                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5122                         squaresLeft[ANY]--;
5123                         piecesLeft[pieceType]--;
5124                         return i;
5125                 }
5126         }
5127         return -1;
5128 }
5129
5130
5131 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5132 // calculate where the next piece goes, (any empty square), and put it there
5133 {
5134         int i;
5135
5136         i = seed % squaresLeft[shade];
5137         nrOfShuffles *= squaresLeft[shade];
5138         seed /= squaresLeft[shade];
5139         put(board, pieceType, rank, i, shade);
5140 }
5141
5142 void AddTwoPieces(Board board, int pieceType, int rank)
5143 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5144 {
5145         int i, n=squaresLeft[ANY], j=n-1, k;
5146
5147         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5148         i = seed % k;  // pick one
5149         nrOfShuffles *= k;
5150         seed /= k;
5151         while(i >= j) i -= j--;
5152         j = n - 1 - j; i += j;
5153         put(board, pieceType, rank, j, ANY);
5154         put(board, pieceType, rank, i, ANY);
5155 }
5156
5157 void SetUpShuffle(Board board, int number)
5158 {
5159         int i, p, first=1;
5160
5161         GetPositionNumber(); nrOfShuffles = 1;
5162
5163         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5164         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5165         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5166
5167         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5168
5169         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5170             p = (int) board[0][i];
5171             if(p < (int) BlackPawn) piecesLeft[p] ++;
5172             board[0][i] = EmptySquare;
5173         }
5174
5175         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5176             // shuffles restricted to allow normal castling put KRR first
5177             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5178                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5179             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5180                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5181             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5182                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5183             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5184                 put(board, WhiteRook, 0, 0, ANY);
5185             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5186         }
5187
5188         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5189             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5190             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5191                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5192                 while(piecesLeft[p] >= 2) {
5193                     AddOnePiece(board, p, 0, LITE);
5194                     AddOnePiece(board, p, 0, DARK);
5195                 }
5196                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5197             }
5198
5199         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5200             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5201             // but we leave King and Rooks for last, to possibly obey FRC restriction
5202             if(p == (int)WhiteRook) continue;
5203             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5204             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5205         }
5206
5207         // now everything is placed, except perhaps King (Unicorn) and Rooks
5208
5209         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5210             // Last King gets castling rights
5211             while(piecesLeft[(int)WhiteUnicorn]) {
5212                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5213                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5214             }
5215
5216             while(piecesLeft[(int)WhiteKing]) {
5217                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5218                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5219             }
5220
5221
5222         } else {
5223             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5224             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5225         }
5226
5227         // Only Rooks can be left; simply place them all
5228         while(piecesLeft[(int)WhiteRook]) {
5229                 i = put(board, WhiteRook, 0, 0, ANY);
5230                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5231                         if(first) {
5232                                 first=0;
5233                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5234                         }
5235                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5236                 }
5237         }
5238         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5239             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5240         }
5241
5242         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5243 }
5244
5245 int SetCharTable( char *table, const char * map )
5246 /* [HGM] moved here from winboard.c because of its general usefulness */
5247 /*       Basically a safe strcpy that uses the last character as King */
5248 {
5249     int result = FALSE; int NrPieces;
5250
5251     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5252                     && NrPieces >= 12 && !(NrPieces&1)) {
5253         int i; /* [HGM] Accept even length from 12 to 34 */
5254
5255         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5256         for( i=0; i<NrPieces/2-1; i++ ) {
5257             table[i] = map[i];
5258             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5259         }
5260         table[(int) WhiteKing]  = map[NrPieces/2-1];
5261         table[(int) BlackKing]  = map[NrPieces-1];
5262
5263         result = TRUE;
5264     }
5265
5266     return result;
5267 }
5268
5269 void Prelude(Board board)
5270 {       // [HGM] superchess: random selection of exo-pieces
5271         int i, j, k; ChessSquare p;
5272         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5273
5274         GetPositionNumber(); // use FRC position number
5275
5276         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5277             SetCharTable(pieceToChar, appData.pieceToCharTable);
5278             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5279                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5280         }
5281
5282         j = seed%4;                 seed /= 4;
5283         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5284         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5285         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5286         j = seed%3 + (seed%3 >= j); seed /= 3;
5287         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5288         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5289         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5290         j = seed%3;                 seed /= 3;
5291         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5292         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5293         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5294         j = seed%2 + (seed%2 >= j); seed /= 2;
5295         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5296         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5297         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5298         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5299         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5300         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5301         put(board, exoPieces[0],    0, 0, ANY);
5302         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5303 }
5304
5305 void
5306 InitPosition(redraw)
5307      int redraw;
5308 {
5309     ChessSquare (* pieces)[BOARD_FILES];
5310     int i, j, pawnRow, overrule,
5311     oldx = gameInfo.boardWidth,
5312     oldy = gameInfo.boardHeight,
5313     oldh = gameInfo.holdingsWidth,
5314     oldv = gameInfo.variant;
5315
5316     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5317
5318     /* [AS] Initialize pv info list [HGM] and game status */
5319     {
5320         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5321             pvInfoList[i].depth = 0;
5322             boards[i][EP_STATUS] = EP_NONE;
5323             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5324         }
5325
5326         initialRulePlies = 0; /* 50-move counter start */
5327
5328         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5329         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5330     }
5331
5332
5333     /* [HGM] logic here is completely changed. In stead of full positions */
5334     /* the initialized data only consist of the two backranks. The switch */
5335     /* selects which one we will use, which is than copied to the Board   */
5336     /* initialPosition, which for the rest is initialized by Pawns and    */
5337     /* empty squares. This initial position is then copied to boards[0],  */
5338     /* possibly after shuffling, so that it remains available.            */
5339
5340     gameInfo.holdingsWidth = 0; /* default board sizes */
5341     gameInfo.boardWidth    = 8;
5342     gameInfo.boardHeight   = 8;
5343     gameInfo.holdingsSize  = 0;
5344     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5345     for(i=0; i<BOARD_FILES-2; i++)
5346       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5347     initialPosition[EP_STATUS] = EP_NONE;
5348     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5349     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5350          SetCharTable(pieceNickName, appData.pieceNickNames);
5351     else SetCharTable(pieceNickName, "............");
5352     pieces = FIDEArray;
5353
5354     switch (gameInfo.variant) {
5355     case VariantFischeRandom:
5356       shuffleOpenings = TRUE;
5357     default:
5358       break;
5359     case VariantShatranj:
5360       pieces = ShatranjArray;
5361       nrCastlingRights = 0;
5362       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5363       break;
5364     case VariantMakruk:
5365       pieces = makrukArray;
5366       nrCastlingRights = 0;
5367       startedFromSetupPosition = TRUE;
5368       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5369       break;
5370     case VariantTwoKings:
5371       pieces = twoKingsArray;
5372       break;
5373     case VariantCapaRandom:
5374       shuffleOpenings = TRUE;
5375     case VariantCapablanca:
5376       pieces = CapablancaArray;
5377       gameInfo.boardWidth = 10;
5378       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5379       break;
5380     case VariantGothic:
5381       pieces = GothicArray;
5382       gameInfo.boardWidth = 10;
5383       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5384       break;
5385     case VariantSChess:
5386       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5387       gameInfo.holdingsSize = 7;
5388       break;
5389     case VariantJanus:
5390       pieces = JanusArray;
5391       gameInfo.boardWidth = 10;
5392       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5393       nrCastlingRights = 6;
5394         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5395         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5396         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5397         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5398         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5399         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5400       break;
5401     case VariantFalcon:
5402       pieces = FalconArray;
5403       gameInfo.boardWidth = 10;
5404       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5405       break;
5406     case VariantXiangqi:
5407       pieces = XiangqiArray;
5408       gameInfo.boardWidth  = 9;
5409       gameInfo.boardHeight = 10;
5410       nrCastlingRights = 0;
5411       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5412       break;
5413     case VariantShogi:
5414       pieces = ShogiArray;
5415       gameInfo.boardWidth  = 9;
5416       gameInfo.boardHeight = 9;
5417       gameInfo.holdingsSize = 7;
5418       nrCastlingRights = 0;
5419       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5420       break;
5421     case VariantCourier:
5422       pieces = CourierArray;
5423       gameInfo.boardWidth  = 12;
5424       nrCastlingRights = 0;
5425       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5426       break;
5427     case VariantKnightmate:
5428       pieces = KnightmateArray;
5429       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5430       break;
5431     case VariantFairy:
5432       pieces = fairyArray;
5433       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5434       break;
5435     case VariantGreat:
5436       pieces = GreatArray;
5437       gameInfo.boardWidth = 10;
5438       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5439       gameInfo.holdingsSize = 8;
5440       break;
5441     case VariantSuper:
5442       pieces = FIDEArray;
5443       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5444       gameInfo.holdingsSize = 8;
5445       startedFromSetupPosition = TRUE;
5446       break;
5447     case VariantCrazyhouse:
5448     case VariantBughouse:
5449       pieces = FIDEArray;
5450       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5451       gameInfo.holdingsSize = 5;
5452       break;
5453     case VariantWildCastle:
5454       pieces = FIDEArray;
5455       /* !!?shuffle with kings guaranteed to be on d or e file */
5456       shuffleOpenings = 1;
5457       break;
5458     case VariantNoCastle:
5459       pieces = FIDEArray;
5460       nrCastlingRights = 0;
5461       /* !!?unconstrained back-rank shuffle */
5462       shuffleOpenings = 1;
5463       break;
5464     }
5465
5466     overrule = 0;
5467     if(appData.NrFiles >= 0) {
5468         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5469         gameInfo.boardWidth = appData.NrFiles;
5470     }
5471     if(appData.NrRanks >= 0) {
5472         gameInfo.boardHeight = appData.NrRanks;
5473     }
5474     if(appData.holdingsSize >= 0) {
5475         i = appData.holdingsSize;
5476         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5477         gameInfo.holdingsSize = i;
5478     }
5479     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5480     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5481         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5482
5483     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5484     if(pawnRow < 1) pawnRow = 1;
5485     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5486
5487     /* User pieceToChar list overrules defaults */
5488     if(appData.pieceToCharTable != NULL)
5489         SetCharTable(pieceToChar, appData.pieceToCharTable);
5490
5491     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5492
5493         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5494             s = (ChessSquare) 0; /* account holding counts in guard band */
5495         for( i=0; i<BOARD_HEIGHT; i++ )
5496             initialPosition[i][j] = s;
5497
5498         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5499         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5500         initialPosition[pawnRow][j] = WhitePawn;
5501         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5502         if(gameInfo.variant == VariantXiangqi) {
5503             if(j&1) {
5504                 initialPosition[pawnRow][j] =
5505                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5506                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5507                    initialPosition[2][j] = WhiteCannon;
5508                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5509                 }
5510             }
5511         }
5512         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5513     }
5514     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5515
5516             j=BOARD_LEFT+1;
5517             initialPosition[1][j] = WhiteBishop;
5518             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5519             j=BOARD_RGHT-2;
5520             initialPosition[1][j] = WhiteRook;
5521             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5522     }
5523
5524     if( nrCastlingRights == -1) {
5525         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5526         /*       This sets default castling rights from none to normal corners   */
5527         /* Variants with other castling rights must set them themselves above    */
5528         nrCastlingRights = 6;
5529
5530         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5531         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5532         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5533         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5534         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5535         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5536      }
5537
5538      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5539      if(gameInfo.variant == VariantGreat) { // promotion commoners
5540         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5541         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5542         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5543         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5544      }
5545      if( gameInfo.variant == VariantSChess ) {
5546       initialPosition[1][0] = BlackMarshall;
5547       initialPosition[2][0] = BlackAngel;
5548       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5549       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5550       initialPosition[1][1] = initialPosition[2][1] = 
5551       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5552      }
5553   if (appData.debugMode) {
5554     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5555   }
5556     if(shuffleOpenings) {
5557         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5558         startedFromSetupPosition = TRUE;
5559     }
5560     if(startedFromPositionFile) {
5561       /* [HGM] loadPos: use PositionFile for every new game */
5562       CopyBoard(initialPosition, filePosition);
5563       for(i=0; i<nrCastlingRights; i++)
5564           initialRights[i] = filePosition[CASTLING][i];
5565       startedFromSetupPosition = TRUE;
5566     }
5567
5568     CopyBoard(boards[0], initialPosition);
5569
5570     if(oldx != gameInfo.boardWidth ||
5571        oldy != gameInfo.boardHeight ||
5572        oldh != gameInfo.holdingsWidth
5573 #ifdef GOTHIC
5574        || oldv == VariantGothic ||        // For licensing popups
5575        gameInfo.variant == VariantGothic
5576 #endif
5577 #ifdef FALCON
5578        || oldv == VariantFalcon ||
5579        gameInfo.variant == VariantFalcon
5580 #endif
5581                                          )
5582             InitDrawingSizes(-2 ,0);
5583
5584     if (redraw)
5585       DrawPosition(TRUE, boards[currentMove]);
5586 }
5587
5588 void
5589 SendBoard(cps, moveNum)
5590      ChessProgramState *cps;
5591      int moveNum;
5592 {
5593     char message[MSG_SIZ];
5594
5595     if (cps->useSetboard) {
5596       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5597       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5598       SendToProgram(message, cps);
5599       free(fen);
5600
5601     } else {
5602       ChessSquare *bp;
5603       int i, j;
5604       /* Kludge to set black to move, avoiding the troublesome and now
5605        * deprecated "black" command.
5606        */
5607       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5608         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5609
5610       SendToProgram("edit\n", cps);
5611       SendToProgram("#\n", cps);
5612       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5613         bp = &boards[moveNum][i][BOARD_LEFT];
5614         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5615           if ((int) *bp < (int) BlackPawn) {
5616             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5617                     AAA + j, ONE + i);
5618             if(message[0] == '+' || message[0] == '~') {
5619               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5620                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5621                         AAA + j, ONE + i);
5622             }
5623             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5624                 message[1] = BOARD_RGHT   - 1 - j + '1';
5625                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5626             }
5627             SendToProgram(message, cps);
5628           }
5629         }
5630       }
5631
5632       SendToProgram("c\n", cps);
5633       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5634         bp = &boards[moveNum][i][BOARD_LEFT];
5635         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5636           if (((int) *bp != (int) EmptySquare)
5637               && ((int) *bp >= (int) BlackPawn)) {
5638             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5639                     AAA + j, ONE + i);
5640             if(message[0] == '+' || message[0] == '~') {
5641               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5642                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5643                         AAA + j, ONE + i);
5644             }
5645             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5646                 message[1] = BOARD_RGHT   - 1 - j + '1';
5647                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5648             }
5649             SendToProgram(message, cps);
5650           }
5651         }
5652       }
5653
5654       SendToProgram(".\n", cps);
5655     }
5656     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5657 }
5658
5659 static int autoQueen; // [HGM] oneclick
5660
5661 int
5662 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5663 {
5664     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5665     /* [HGM] add Shogi promotions */
5666     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5667     ChessSquare piece;
5668     ChessMove moveType;
5669     Boolean premove;
5670
5671     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5672     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5673
5674     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5675       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5676         return FALSE;
5677
5678     piece = boards[currentMove][fromY][fromX];
5679     if(gameInfo.variant == VariantShogi) {
5680         promotionZoneSize = BOARD_HEIGHT/3;
5681         highestPromotingPiece = (int)WhiteFerz;
5682     } else if(gameInfo.variant == VariantMakruk) {
5683         promotionZoneSize = 3;
5684     }
5685
5686     // next weed out all moves that do not touch the promotion zone at all
5687     if((int)piece >= BlackPawn) {
5688         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5689              return FALSE;
5690         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5691     } else {
5692         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5693            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5694     }
5695
5696     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5697
5698     // weed out mandatory Shogi promotions
5699     if(gameInfo.variant == VariantShogi) {
5700         if(piece >= BlackPawn) {
5701             if(toY == 0 && piece == BlackPawn ||
5702                toY == 0 && piece == BlackQueen ||
5703                toY <= 1 && piece == BlackKnight) {
5704                 *promoChoice = '+';
5705                 return FALSE;
5706             }
5707         } else {
5708             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5709                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5710                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5711                 *promoChoice = '+';
5712                 return FALSE;
5713             }
5714         }
5715     }
5716
5717     // weed out obviously illegal Pawn moves
5718     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5719         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5720         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5721         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5722         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5723         // note we are not allowed to test for valid (non-)capture, due to premove
5724     }
5725
5726     // we either have a choice what to promote to, or (in Shogi) whether to promote
5727     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5728         *promoChoice = PieceToChar(BlackFerz);  // no choice
5729         return FALSE;
5730     }
5731     // no sense asking what we must promote to if it is going to explode...
5732     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5733         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5734         return FALSE;
5735     }
5736     if(autoQueen) { // predetermined
5737         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5738              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5739         else *promoChoice = PieceToChar(BlackQueen);
5740         return FALSE;
5741     }
5742
5743     // suppress promotion popup on illegal moves that are not premoves
5744     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5745               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5746     if(appData.testLegality && !premove) {
5747         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5748                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5749         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5750             return FALSE;
5751     }
5752
5753     return TRUE;
5754 }
5755
5756 int
5757 InPalace(row, column)
5758      int row, column;
5759 {   /* [HGM] for Xiangqi */
5760     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5761          column < (BOARD_WIDTH + 4)/2 &&
5762          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5763     return FALSE;
5764 }
5765
5766 int
5767 PieceForSquare (x, y)
5768      int x;
5769      int y;
5770 {
5771   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5772      return -1;
5773   else
5774      return boards[currentMove][y][x];
5775 }
5776
5777 int
5778 OKToStartUserMove(x, y)
5779      int x, y;
5780 {
5781     ChessSquare from_piece;
5782     int white_piece;
5783
5784     if (matchMode) return FALSE;
5785     if (gameMode == EditPosition) return TRUE;
5786
5787     if (x >= 0 && y >= 0)
5788       from_piece = boards[currentMove][y][x];
5789     else
5790       from_piece = EmptySquare;
5791
5792     if (from_piece == EmptySquare) return FALSE;
5793
5794     white_piece = (int)from_piece >= (int)WhitePawn &&
5795       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5796
5797     switch (gameMode) {
5798       case PlayFromGameFile:
5799       case AnalyzeFile:
5800       case TwoMachinesPlay:
5801       case EndOfGame:
5802         return FALSE;
5803
5804       case IcsObserving:
5805       case IcsIdle:
5806         return FALSE;
5807
5808       case MachinePlaysWhite:
5809       case IcsPlayingBlack:
5810         if (appData.zippyPlay) return FALSE;
5811         if (white_piece) {
5812             DisplayMoveError(_("You are playing Black"));
5813             return FALSE;
5814         }
5815         break;
5816
5817       case MachinePlaysBlack:
5818       case IcsPlayingWhite:
5819         if (appData.zippyPlay) return FALSE;
5820         if (!white_piece) {
5821             DisplayMoveError(_("You are playing White"));
5822             return FALSE;
5823         }
5824         break;
5825
5826       case EditGame:
5827         if (!white_piece && WhiteOnMove(currentMove)) {
5828             DisplayMoveError(_("It is White's turn"));
5829             return FALSE;
5830         }
5831         if (white_piece && !WhiteOnMove(currentMove)) {
5832             DisplayMoveError(_("It is Black's turn"));
5833             return FALSE;
5834         }
5835         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5836             /* Editing correspondence game history */
5837             /* Could disallow this or prompt for confirmation */
5838             cmailOldMove = -1;
5839         }
5840         break;
5841
5842       case BeginningOfGame:
5843         if (appData.icsActive) return FALSE;
5844         if (!appData.noChessProgram) {
5845             if (!white_piece) {
5846                 DisplayMoveError(_("You are playing White"));
5847                 return FALSE;
5848             }
5849         }
5850         break;
5851
5852       case Training:
5853         if (!white_piece && WhiteOnMove(currentMove)) {
5854             DisplayMoveError(_("It is White's turn"));
5855             return FALSE;
5856         }
5857         if (white_piece && !WhiteOnMove(currentMove)) {
5858             DisplayMoveError(_("It is Black's turn"));
5859             return FALSE;
5860         }
5861         break;
5862
5863       default:
5864       case IcsExamining:
5865         break;
5866     }
5867     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5868         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5869         && gameMode != AnalyzeFile && gameMode != Training) {
5870         DisplayMoveError(_("Displayed position is not current"));
5871         return FALSE;
5872     }
5873     return TRUE;
5874 }
5875
5876 Boolean
5877 OnlyMove(int *x, int *y, Boolean captures) {
5878     DisambiguateClosure cl;
5879     if (appData.zippyPlay) return FALSE;
5880     switch(gameMode) {
5881       case MachinePlaysBlack:
5882       case IcsPlayingWhite:
5883       case BeginningOfGame:
5884         if(!WhiteOnMove(currentMove)) return FALSE;
5885         break;
5886       case MachinePlaysWhite:
5887       case IcsPlayingBlack:
5888         if(WhiteOnMove(currentMove)) return FALSE;
5889         break;
5890       case EditGame:
5891         break;
5892       default:
5893         return FALSE;
5894     }
5895     cl.pieceIn = EmptySquare;
5896     cl.rfIn = *y;
5897     cl.ffIn = *x;
5898     cl.rtIn = -1;
5899     cl.ftIn = -1;
5900     cl.promoCharIn = NULLCHAR;
5901     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5902     if( cl.kind == NormalMove ||
5903         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5904         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5905         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5906       fromX = cl.ff;
5907       fromY = cl.rf;
5908       *x = cl.ft;
5909       *y = cl.rt;
5910       return TRUE;
5911     }
5912     if(cl.kind != ImpossibleMove) return FALSE;
5913     cl.pieceIn = EmptySquare;
5914     cl.rfIn = -1;
5915     cl.ffIn = -1;
5916     cl.rtIn = *y;
5917     cl.ftIn = *x;
5918     cl.promoCharIn = NULLCHAR;
5919     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5920     if( cl.kind == NormalMove ||
5921         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5922         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5923         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5924       fromX = cl.ff;
5925       fromY = cl.rf;
5926       *x = cl.ft;
5927       *y = cl.rt;
5928       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5929       return TRUE;
5930     }
5931     return FALSE;
5932 }
5933
5934 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5935 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5936 int lastLoadGameUseList = FALSE;
5937 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5938 ChessMove lastLoadGameStart = EndOfFile;
5939
5940 void
5941 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5942      int fromX, fromY, toX, toY;
5943      int promoChar;
5944 {
5945     ChessMove moveType;
5946     ChessSquare pdown, pup;
5947
5948     /* Check if the user is playing in turn.  This is complicated because we
5949        let the user "pick up" a piece before it is his turn.  So the piece he
5950        tried to pick up may have been captured by the time he puts it down!
5951        Therefore we use the color the user is supposed to be playing in this
5952        test, not the color of the piece that is currently on the starting
5953        square---except in EditGame mode, where the user is playing both
5954        sides; fortunately there the capture race can't happen.  (It can
5955        now happen in IcsExamining mode, but that's just too bad.  The user
5956        will get a somewhat confusing message in that case.)
5957        */
5958
5959     switch (gameMode) {
5960       case PlayFromGameFile:
5961       case AnalyzeFile:
5962       case TwoMachinesPlay:
5963       case EndOfGame:
5964       case IcsObserving:
5965       case IcsIdle:
5966         /* We switched into a game mode where moves are not accepted,
5967            perhaps while the mouse button was down. */
5968         return;
5969
5970       case MachinePlaysWhite:
5971         /* User is moving for Black */
5972         if (WhiteOnMove(currentMove)) {
5973             DisplayMoveError(_("It is White's turn"));
5974             return;
5975         }
5976         break;
5977
5978       case MachinePlaysBlack:
5979         /* User is moving for White */
5980         if (!WhiteOnMove(currentMove)) {
5981             DisplayMoveError(_("It is Black's turn"));
5982             return;
5983         }
5984         break;
5985
5986       case EditGame:
5987       case IcsExamining:
5988       case BeginningOfGame:
5989       case AnalyzeMode:
5990       case Training:
5991         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5992             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5993             /* User is moving for Black */
5994             if (WhiteOnMove(currentMove)) {
5995                 DisplayMoveError(_("It is White's turn"));
5996                 return;
5997             }
5998         } else {
5999             /* User is moving for White */
6000             if (!WhiteOnMove(currentMove)) {
6001                 DisplayMoveError(_("It is Black's turn"));
6002                 return;
6003             }
6004         }
6005         break;
6006
6007       case IcsPlayingBlack:
6008         /* User is moving for Black */
6009         if (WhiteOnMove(currentMove)) {
6010             if (!appData.premove) {
6011                 DisplayMoveError(_("It is White's turn"));
6012             } else if (toX >= 0 && toY >= 0) {
6013                 premoveToX = toX;
6014                 premoveToY = toY;
6015                 premoveFromX = fromX;
6016                 premoveFromY = fromY;
6017                 premovePromoChar = promoChar;
6018                 gotPremove = 1;
6019                 if (appData.debugMode)
6020                     fprintf(debugFP, "Got premove: fromX %d,"
6021                             "fromY %d, toX %d, toY %d\n",
6022                             fromX, fromY, toX, toY);
6023             }
6024             return;
6025         }
6026         break;
6027
6028       case IcsPlayingWhite:
6029         /* User is moving for White */
6030         if (!WhiteOnMove(currentMove)) {
6031             if (!appData.premove) {
6032                 DisplayMoveError(_("It is Black's turn"));
6033             } else if (toX >= 0 && toY >= 0) {
6034                 premoveToX = toX;
6035                 premoveToY = toY;
6036                 premoveFromX = fromX;
6037                 premoveFromY = fromY;
6038                 premovePromoChar = promoChar;
6039                 gotPremove = 1;
6040                 if (appData.debugMode)
6041                     fprintf(debugFP, "Got premove: fromX %d,"
6042                             "fromY %d, toX %d, toY %d\n",
6043                             fromX, fromY, toX, toY);
6044             }
6045             return;
6046         }
6047         break;
6048
6049       default:
6050         break;
6051
6052       case EditPosition:
6053         /* EditPosition, empty square, or different color piece;
6054            click-click move is possible */
6055         if (toX == -2 || toY == -2) {
6056             boards[0][fromY][fromX] = EmptySquare;
6057             DrawPosition(FALSE, boards[currentMove]);
6058             return;
6059         } else if (toX >= 0 && toY >= 0) {
6060             boards[0][toY][toX] = boards[0][fromY][fromX];
6061             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6062                 if(boards[0][fromY][0] != EmptySquare) {
6063                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6064                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6065                 }
6066             } else
6067             if(fromX == BOARD_RGHT+1) {
6068                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6069                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6070                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6071                 }
6072             } else
6073             boards[0][fromY][fromX] = EmptySquare;
6074             DrawPosition(FALSE, boards[currentMove]);
6075             return;
6076         }
6077         return;
6078     }
6079
6080     if(toX < 0 || toY < 0) return;
6081     pdown = boards[currentMove][fromY][fromX];
6082     pup = boards[currentMove][toY][toX];
6083
6084     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6085     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
6086          if( pup != EmptySquare ) return;
6087          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6088            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6089                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6090            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6091            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6092            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6093            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6094          fromY = DROP_RANK;
6095     }
6096
6097     /* [HGM] always test for legality, to get promotion info */
6098     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6099                                          fromY, fromX, toY, toX, promoChar);
6100     /* [HGM] but possibly ignore an IllegalMove result */
6101     if (appData.testLegality) {
6102         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6103             DisplayMoveError(_("Illegal move"));
6104             return;
6105         }
6106     }
6107
6108     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6109 }
6110
6111 /* Common tail of UserMoveEvent and DropMenuEvent */
6112 int
6113 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6114      ChessMove moveType;
6115      int fromX, fromY, toX, toY;
6116      /*char*/int promoChar;
6117 {
6118     char *bookHit = 0;
6119
6120     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6121         // [HGM] superchess: suppress promotions to non-available piece
6122         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6123         if(WhiteOnMove(currentMove)) {
6124             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6125         } else {
6126             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6127         }
6128     }
6129
6130     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6131        move type in caller when we know the move is a legal promotion */
6132     if(moveType == NormalMove && promoChar)
6133         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6134
6135     /* [HGM] <popupFix> The following if has been moved here from
6136        UserMoveEvent(). Because it seemed to belong here (why not allow
6137        piece drops in training games?), and because it can only be
6138        performed after it is known to what we promote. */
6139     if (gameMode == Training) {
6140       /* compare the move played on the board to the next move in the
6141        * game. If they match, display the move and the opponent's response.
6142        * If they don't match, display an error message.
6143        */
6144       int saveAnimate;
6145       Board testBoard;
6146       CopyBoard(testBoard, boards[currentMove]);
6147       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6148
6149       if (CompareBoards(testBoard, boards[currentMove+1])) {
6150         ForwardInner(currentMove+1);
6151
6152         /* Autoplay the opponent's response.
6153          * if appData.animate was TRUE when Training mode was entered,
6154          * the response will be animated.
6155          */
6156         saveAnimate = appData.animate;
6157         appData.animate = animateTraining;
6158         ForwardInner(currentMove+1);
6159         appData.animate = saveAnimate;
6160
6161         /* check for the end of the game */
6162         if (currentMove >= forwardMostMove) {
6163           gameMode = PlayFromGameFile;
6164           ModeHighlight();
6165           SetTrainingModeOff();
6166           DisplayInformation(_("End of game"));
6167         }
6168       } else {
6169         DisplayError(_("Incorrect move"), 0);
6170       }
6171       return 1;
6172     }
6173
6174   /* Ok, now we know that the move is good, so we can kill
6175      the previous line in Analysis Mode */
6176   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6177                                 && currentMove < forwardMostMove) {
6178     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6179     else forwardMostMove = currentMove;
6180   }
6181
6182   /* If we need the chess program but it's dead, restart it */
6183   ResurrectChessProgram();
6184
6185   /* A user move restarts a paused game*/
6186   if (pausing)
6187     PauseEvent();
6188
6189   thinkOutput[0] = NULLCHAR;
6190
6191   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6192
6193   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6194
6195   if (gameMode == BeginningOfGame) {
6196     if (appData.noChessProgram) {
6197       gameMode = EditGame;
6198       SetGameInfo();
6199     } else {
6200       char buf[MSG_SIZ];
6201       gameMode = MachinePlaysBlack;
6202       StartClocks();
6203       SetGameInfo();
6204       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6205       DisplayTitle(buf);
6206       if (first.sendName) {
6207         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6208         SendToProgram(buf, &first);
6209       }
6210       StartClocks();
6211     }
6212     ModeHighlight();
6213   }
6214
6215   /* Relay move to ICS or chess engine */
6216   if (appData.icsActive) {
6217     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6218         gameMode == IcsExamining) {
6219       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6220         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6221         SendToICS("draw ");
6222         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6223       }
6224       // also send plain move, in case ICS does not understand atomic claims
6225       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6226       ics_user_moved = 1;
6227     }
6228   } else {
6229     if (first.sendTime && (gameMode == BeginningOfGame ||
6230                            gameMode == MachinePlaysWhite ||
6231                            gameMode == MachinePlaysBlack)) {
6232       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6233     }
6234     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6235          // [HGM] book: if program might be playing, let it use book
6236         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6237         first.maybeThinking = TRUE;
6238     } else SendMoveToProgram(forwardMostMove-1, &first);
6239     if (currentMove == cmailOldMove + 1) {
6240       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6241     }
6242   }
6243
6244   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6245
6246   switch (gameMode) {
6247   case EditGame:
6248     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6249     case MT_NONE:
6250     case MT_CHECK:
6251       break;
6252     case MT_CHECKMATE:
6253     case MT_STAINMATE:
6254       if (WhiteOnMove(currentMove)) {
6255         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6256       } else {
6257         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6258       }
6259       break;
6260     case MT_STALEMATE:
6261       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6262       break;
6263     }
6264     break;
6265
6266   case MachinePlaysBlack:
6267   case MachinePlaysWhite:
6268     /* disable certain menu options while machine is thinking */
6269     SetMachineThinkingEnables();
6270     break;
6271
6272   default:
6273     break;
6274   }
6275
6276   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6277
6278   if(bookHit) { // [HGM] book: simulate book reply
6279         static char bookMove[MSG_SIZ]; // a bit generous?
6280
6281         programStats.nodes = programStats.depth = programStats.time =
6282         programStats.score = programStats.got_only_move = 0;
6283         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6284
6285         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6286         strcat(bookMove, bookHit);
6287         HandleMachineMove(bookMove, &first);
6288   }
6289   return 1;
6290 }
6291
6292 void
6293 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6294      Board board;
6295      int flags;
6296      ChessMove kind;
6297      int rf, ff, rt, ft;
6298      VOIDSTAR closure;
6299 {
6300     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6301     Markers *m = (Markers *) closure;
6302     if(rf == fromY && ff == fromX)
6303         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6304                          || kind == WhiteCapturesEnPassant
6305                          || kind == BlackCapturesEnPassant);
6306     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6307 }
6308
6309 void
6310 MarkTargetSquares(int clear)
6311 {
6312   int x, y;
6313   if(!appData.markers || !appData.highlightDragging ||
6314      !appData.testLegality || gameMode == EditPosition) return;
6315   if(clear) {
6316     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6317   } else {
6318     int capt = 0;
6319     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6320     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6321       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6322       if(capt)
6323       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6324     }
6325   }
6326   DrawPosition(TRUE, NULL);
6327 }
6328
6329 int
6330 Explode(Board board, int fromX, int fromY, int toX, int toY)
6331 {
6332     if(gameInfo.variant == VariantAtomic &&
6333        (board[toY][toX] != EmptySquare ||                     // capture?
6334         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6335                          board[fromY][fromX] == BlackPawn   )
6336       )) {
6337         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6338         return TRUE;
6339     }
6340     return FALSE;
6341 }
6342
6343 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6344
6345 void LeftClick(ClickType clickType, int xPix, int yPix)
6346 {
6347     int x, y;
6348     Boolean saveAnimate;
6349     static int second = 0, promotionChoice = 0, dragging = 0;
6350     char promoChoice = NULLCHAR;
6351
6352     if(appData.seekGraph && appData.icsActive && loggedOn &&
6353         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6354         SeekGraphClick(clickType, xPix, yPix, 0);
6355         return;
6356     }
6357
6358     if (clickType == Press) ErrorPopDown();
6359     MarkTargetSquares(1);
6360
6361     x = EventToSquare(xPix, BOARD_WIDTH);
6362     y = EventToSquare(yPix, BOARD_HEIGHT);
6363     if (!flipView && y >= 0) {
6364         y = BOARD_HEIGHT - 1 - y;
6365     }
6366     if (flipView && x >= 0) {
6367         x = BOARD_WIDTH - 1 - x;
6368     }
6369
6370     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6371         if(clickType == Release) return; // ignore upclick of click-click destination
6372         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6373         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6374         if(gameInfo.holdingsWidth &&
6375                 (WhiteOnMove(currentMove)
6376                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6377                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6378             // click in right holdings, for determining promotion piece
6379             ChessSquare p = boards[currentMove][y][x];
6380             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6381             if(p != EmptySquare) {
6382                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6383                 fromX = fromY = -1;
6384                 return;
6385             }
6386         }
6387         DrawPosition(FALSE, boards[currentMove]);
6388         return;
6389     }
6390
6391     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6392     if(clickType == Press
6393             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6394               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6395               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6396         return;
6397
6398     autoQueen = appData.alwaysPromoteToQueen;
6399
6400     if (fromX == -1) {
6401       gatingPiece = EmptySquare;
6402       if (clickType != Press) {
6403         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6404             DragPieceEnd(xPix, yPix); dragging = 0;
6405             DrawPosition(FALSE, NULL);
6406         }
6407         return;
6408       }
6409       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6410             /* First square */
6411             if (OKToStartUserMove(x, y)) {
6412                 fromX = x;
6413                 fromY = y;
6414                 second = 0;
6415                 MarkTargetSquares(0);
6416                 DragPieceBegin(xPix, yPix); dragging = 1;
6417                 if (appData.highlightDragging) {
6418                     SetHighlights(x, y, -1, -1);
6419                 }
6420             }
6421             return;
6422         }
6423     }
6424
6425     /* fromX != -1 */
6426     if (clickType == Press && gameMode != EditPosition) {
6427         ChessSquare fromP;
6428         ChessSquare toP;
6429         int frc;
6430
6431         // ignore off-board to clicks
6432         if(y < 0 || x < 0) return;
6433
6434         /* Check if clicking again on the same color piece */
6435         fromP = boards[currentMove][fromY][fromX];
6436         toP = boards[currentMove][y][x];
6437         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6438         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6439              WhitePawn <= toP && toP <= WhiteKing &&
6440              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6441              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6442             (BlackPawn <= fromP && fromP <= BlackKing &&
6443              BlackPawn <= toP && toP <= BlackKing &&
6444              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6445              !(fromP == BlackKing && toP == BlackRook && frc))) {
6446             /* Clicked again on same color piece -- changed his mind */
6447             second = (x == fromX && y == fromY);
6448            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6449             if (appData.highlightDragging) {
6450                 SetHighlights(x, y, -1, -1);
6451             } else {
6452                 ClearHighlights();
6453             }
6454             if (OKToStartUserMove(x, y)) {
6455                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6456                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6457                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6458                  gatingPiece = boards[currentMove][fromY][fromX];
6459                 else gatingPiece = EmptySquare;
6460                 fromX = x;
6461                 fromY = y; dragging = 1;
6462                 MarkTargetSquares(0);
6463                 DragPieceBegin(xPix, yPix);
6464             }
6465            }
6466            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6467            second = FALSE; 
6468         }
6469         // ignore clicks on holdings
6470         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6471     }
6472
6473     if (clickType == Release && x == fromX && y == fromY) {
6474         DragPieceEnd(xPix, yPix); dragging = 0;
6475         if (appData.animateDragging) {
6476             /* Undo animation damage if any */
6477             DrawPosition(FALSE, NULL);
6478         }
6479         if (second) {
6480             /* Second up/down in same square; just abort move */
6481             second = 0;
6482             fromX = fromY = -1;
6483             gatingPiece = EmptySquare;
6484             ClearHighlights();
6485             gotPremove = 0;
6486             ClearPremoveHighlights();
6487         } else {
6488             /* First upclick in same square; start click-click mode */
6489             SetHighlights(x, y, -1, -1);
6490         }
6491         return;
6492     }
6493
6494     /* we now have a different from- and (possibly off-board) to-square */
6495     /* Completed move */
6496     toX = x;
6497     toY = y;
6498     saveAnimate = appData.animate;
6499     if (clickType == Press) {
6500         /* Finish clickclick move */
6501         if (appData.animate || appData.highlightLastMove) {
6502             SetHighlights(fromX, fromY, toX, toY);
6503         } else {
6504             ClearHighlights();
6505         }
6506     } else {
6507         /* Finish drag move */
6508         if (appData.highlightLastMove) {
6509             SetHighlights(fromX, fromY, toX, toY);
6510         } else {
6511             ClearHighlights();
6512         }
6513         DragPieceEnd(xPix, yPix); dragging = 0;
6514         /* Don't animate move and drag both */
6515         appData.animate = FALSE;
6516     }
6517
6518     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6519     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6520         ChessSquare piece = boards[currentMove][fromY][fromX];
6521         if(gameMode == EditPosition && piece != EmptySquare &&
6522            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6523             int n;
6524
6525             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6526                 n = PieceToNumber(piece - (int)BlackPawn);
6527                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6528                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6529                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6530             } else
6531             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6532                 n = PieceToNumber(piece);
6533                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6534                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6535                 boards[currentMove][n][BOARD_WIDTH-2]++;
6536             }
6537             boards[currentMove][fromY][fromX] = EmptySquare;
6538         }
6539         ClearHighlights();
6540         fromX = fromY = -1;
6541         DrawPosition(TRUE, boards[currentMove]);
6542         return;
6543     }
6544
6545     // off-board moves should not be highlighted
6546     if(x < 0 || y < 0) ClearHighlights();
6547
6548     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6549
6550     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6551         SetHighlights(fromX, fromY, toX, toY);
6552         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6553             // [HGM] super: promotion to captured piece selected from holdings
6554             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6555             promotionChoice = TRUE;
6556             // kludge follows to temporarily execute move on display, without promoting yet
6557             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6558             boards[currentMove][toY][toX] = p;
6559             DrawPosition(FALSE, boards[currentMove]);
6560             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6561             boards[currentMove][toY][toX] = q;
6562             DisplayMessage("Click in holdings to choose piece", "");
6563             return;
6564         }
6565         PromotionPopUp();
6566     } else {
6567         int oldMove = currentMove;
6568         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6569         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6570         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6571         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6572            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6573             DrawPosition(TRUE, boards[currentMove]);
6574         fromX = fromY = -1;
6575     }
6576     appData.animate = saveAnimate;
6577     if (appData.animate || appData.animateDragging) {
6578         /* Undo animation damage if needed */
6579         DrawPosition(FALSE, NULL);
6580     }
6581 }
6582
6583 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6584 {   // front-end-free part taken out of PieceMenuPopup
6585     int whichMenu; int xSqr, ySqr;
6586
6587     if(seekGraphUp) { // [HGM] seekgraph
6588         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6589         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6590         return -2;
6591     }
6592
6593     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6594          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6595         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6596         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6597         if(action == Press)   {
6598             originalFlip = flipView;
6599             flipView = !flipView; // temporarily flip board to see game from partners perspective
6600             DrawPosition(TRUE, partnerBoard);
6601             DisplayMessage(partnerStatus, "");
6602             partnerUp = TRUE;
6603         } else if(action == Release) {
6604             flipView = originalFlip;
6605             DrawPosition(TRUE, boards[currentMove]);
6606             partnerUp = FALSE;
6607         }
6608         return -2;
6609     }
6610
6611     xSqr = EventToSquare(x, BOARD_WIDTH);
6612     ySqr = EventToSquare(y, BOARD_HEIGHT);
6613     if (action == Release) UnLoadPV(); // [HGM] pv
6614     if (action != Press) return -2; // return code to be ignored
6615     switch (gameMode) {
6616       case IcsExamining:
6617         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6618       case EditPosition:
6619         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6620         if (xSqr < 0 || ySqr < 0) return -1;\r
6621         whichMenu = 0; // edit-position menu
6622         break;
6623       case IcsObserving:
6624         if(!appData.icsEngineAnalyze) return -1;
6625       case IcsPlayingWhite:
6626       case IcsPlayingBlack:
6627         if(!appData.zippyPlay) goto noZip;
6628       case AnalyzeMode:
6629       case AnalyzeFile:
6630       case MachinePlaysWhite:
6631       case MachinePlaysBlack:
6632       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6633         if (!appData.dropMenu) {
6634           LoadPV(x, y);
6635           return 2; // flag front-end to grab mouse events
6636         }
6637         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6638            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6639       case EditGame:
6640       noZip:
6641         if (xSqr < 0 || ySqr < 0) return -1;
6642         if (!appData.dropMenu || appData.testLegality &&
6643             gameInfo.variant != VariantBughouse &&
6644             gameInfo.variant != VariantCrazyhouse) return -1;
6645         whichMenu = 1; // drop menu
6646         break;
6647       default:
6648         return -1;
6649     }
6650
6651     if (((*fromX = xSqr) < 0) ||
6652         ((*fromY = ySqr) < 0)) {
6653         *fromX = *fromY = -1;
6654         return -1;
6655     }
6656     if (flipView)
6657       *fromX = BOARD_WIDTH - 1 - *fromX;
6658     else
6659       *fromY = BOARD_HEIGHT - 1 - *fromY;
6660
6661     return whichMenu;
6662 }
6663
6664 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6665 {
6666 //    char * hint = lastHint;
6667     FrontEndProgramStats stats;
6668
6669     stats.which = cps == &first ? 0 : 1;
6670     stats.depth = cpstats->depth;
6671     stats.nodes = cpstats->nodes;
6672     stats.score = cpstats->score;
6673     stats.time = cpstats->time;
6674     stats.pv = cpstats->movelist;
6675     stats.hint = lastHint;
6676     stats.an_move_index = 0;
6677     stats.an_move_count = 0;
6678
6679     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6680         stats.hint = cpstats->move_name;
6681         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6682         stats.an_move_count = cpstats->nr_moves;
6683     }
6684
6685     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
6686
6687     SetProgramStats( &stats );
6688 }
6689
6690 void
6691 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6692 {       // count all piece types
6693         int p, f, r;
6694         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6695         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6696         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6697                 p = board[r][f];
6698                 pCnt[p]++;
6699                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6700                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6701                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6702                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6703                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6704                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6705         }
6706 }
6707
6708 int
6709 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6710 {
6711         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6712         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6713
6714         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6715         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6716         if(myPawns == 2 && nMine == 3) // KPP
6717             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6718         if(myPawns == 1 && nMine == 2) // KP
6719             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6720         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6721             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6722         if(myPawns) return FALSE;
6723         if(pCnt[WhiteRook+side])
6724             return pCnt[BlackRook-side] ||
6725                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6726                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6727                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6728         if(pCnt[WhiteCannon+side]) {
6729             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6730             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6731         }
6732         if(pCnt[WhiteKnight+side])
6733             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6734         return FALSE;
6735 }
6736
6737 int
6738 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6739 {
6740         VariantClass v = gameInfo.variant;
6741
6742         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6743         if(v == VariantShatranj) return TRUE; // always winnable through baring
6744         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6745         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6746
6747         if(v == VariantXiangqi) {
6748                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6749
6750                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6751                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6752                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6753                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6754                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6755                 if(stale) // we have at least one last-rank P plus perhaps C
6756                     return majors // KPKX
6757                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6758                 else // KCA*E*
6759                     return pCnt[WhiteFerz+side] // KCAK
6760                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6761                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6762                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6763
6764         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6765                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6766
6767                 if(nMine == 1) return FALSE; // bare King
6768                 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
6769                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6770                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6771                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6772                 if(pCnt[WhiteKnight+side])
6773                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6774                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6775                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6776                 if(nBishops)
6777                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6778                 if(pCnt[WhiteAlfil+side])
6779                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6780                 if(pCnt[WhiteWazir+side])
6781                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6782         }
6783
6784         return TRUE;
6785 }
6786
6787 int
6788 Adjudicate(ChessProgramState *cps)
6789 {       // [HGM] some adjudications useful with buggy engines
6790         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6791         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6792         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6793         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6794         int k, count = 0; static int bare = 1;
6795         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6796         Boolean canAdjudicate = !appData.icsActive;
6797
6798         // most tests only when we understand the game, i.e. legality-checking on
6799             if( appData.testLegality )
6800             {   /* [HGM] Some more adjudications for obstinate engines */
6801                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6802                 static int moveCount = 6;
6803                 ChessMove result;
6804                 char *reason = NULL;
6805
6806                 /* Count what is on board. */
6807                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6808
6809                 /* Some material-based adjudications that have to be made before stalemate test */
6810                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6811                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6812                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6813                      if(canAdjudicate && appData.checkMates) {
6814                          if(engineOpponent)
6815                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6816                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6817                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6818                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6819                          return 1;
6820                      }
6821                 }
6822
6823                 /* Bare King in Shatranj (loses) or Losers (wins) */
6824                 if( nrW == 1 || nrB == 1) {
6825                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6826                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6827                      if(canAdjudicate && appData.checkMates) {
6828                          if(engineOpponent)
6829                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6830                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6831                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6832                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6833                          return 1;
6834                      }
6835                   } else
6836                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6837                   {    /* bare King */
6838                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6839                         if(canAdjudicate && appData.checkMates) {
6840                             /* but only adjudicate if adjudication enabled */
6841                             if(engineOpponent)
6842                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6843                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6844                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6845                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6846                             return 1;
6847                         }
6848                   }
6849                 } else bare = 1;
6850
6851
6852             // don't wait for engine to announce game end if we can judge ourselves
6853             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6854               case MT_CHECK:
6855                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6856                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6857                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6858                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6859                             checkCnt++;
6860                         if(checkCnt >= 2) {
6861                             reason = "Xboard adjudication: 3rd check";
6862                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6863                             break;
6864                         }
6865                     }
6866                 }
6867               case MT_NONE:
6868               default:
6869                 break;
6870               case MT_STALEMATE:
6871               case MT_STAINMATE:
6872                 reason = "Xboard adjudication: Stalemate";
6873                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6874                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6875                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6876                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6877                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6878                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6879                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6880                                                                         EP_CHECKMATE : EP_WINS);
6881                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6882                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6883                 }
6884                 break;
6885               case MT_CHECKMATE:
6886                 reason = "Xboard adjudication: Checkmate";
6887                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6888                 break;
6889             }
6890
6891                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6892                     case EP_STALEMATE:
6893                         result = GameIsDrawn; break;
6894                     case EP_CHECKMATE:
6895                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6896                     case EP_WINS:
6897                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6898                     default:
6899                         result = EndOfFile;
6900                 }
6901                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6902                     if(engineOpponent)
6903                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6904                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6905                     GameEnds( result, reason, GE_XBOARD );
6906                     return 1;
6907                 }
6908
6909                 /* Next absolutely insufficient mating material. */
6910                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6911                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6912                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6913
6914                      /* always flag draws, for judging claims */
6915                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6916
6917                      if(canAdjudicate && appData.materialDraws) {
6918                          /* but only adjudicate them if adjudication enabled */
6919                          if(engineOpponent) {
6920                            SendToProgram("force\n", engineOpponent); // suppress reply
6921                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6922                          }
6923                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6924                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6925                          return 1;
6926                      }
6927                 }
6928
6929                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6930                 if(gameInfo.variant == VariantXiangqi ?
6931                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6932                  : nrW + nrB == 4 &&
6933                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6934                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6935                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6936                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6937                    ) ) {
6938                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6939                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6940                           if(engineOpponent) {
6941                             SendToProgram("force\n", engineOpponent); // suppress reply
6942                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6943                           }
6944                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6945                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6946                           return 1;
6947                      }
6948                 } else moveCount = 6;
6949             }
6950         if (appData.debugMode) { int i;
6951             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6952                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6953                     appData.drawRepeats);
6954             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6955               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6956
6957         }
6958
6959         // Repetition draws and 50-move rule can be applied independently of legality testing
6960
6961                 /* Check for rep-draws */
6962                 count = 0;
6963                 for(k = forwardMostMove-2;
6964                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6965                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6966                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6967                     k-=2)
6968                 {   int rights=0;
6969                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6970                         /* compare castling rights */
6971                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6972                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6973                                 rights++; /* King lost rights, while rook still had them */
6974                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6975                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6976                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6977                                    rights++; /* but at least one rook lost them */
6978                         }
6979                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6980                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6981                                 rights++;
6982                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6983                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6984                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6985                                    rights++;
6986                         }
6987                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6988                             && appData.drawRepeats > 1) {
6989                              /* adjudicate after user-specified nr of repeats */
6990                              int result = GameIsDrawn;
6991                              char *details = "XBoard adjudication: repetition draw";
6992                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6993                                 // [HGM] xiangqi: check for forbidden perpetuals
6994                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6995                                 for(m=forwardMostMove; m>k; m-=2) {
6996                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6997                                         ourPerpetual = 0; // the current mover did not always check
6998                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6999                                         hisPerpetual = 0; // the opponent did not always check
7000                                 }
7001                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7002                                                                         ourPerpetual, hisPerpetual);
7003                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7004                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7005                                     details = "Xboard adjudication: perpetual checking";
7006                                 } else
7007                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7008                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7009                                 } else
7010                                 // Now check for perpetual chases
7011                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7012                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7013                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7014                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7015                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7016                                         details = "Xboard adjudication: perpetual chasing";
7017                                     } else
7018                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7019                                         break; // Abort repetition-checking loop.
7020                                 }
7021                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7022                              }
7023                              if(engineOpponent) {
7024                                SendToProgram("force\n", engineOpponent); // suppress reply
7025                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7026                              }
7027                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7028                              GameEnds( result, details, GE_XBOARD );
7029                              return 1;
7030                         }
7031                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7032                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7033                     }
7034                 }
7035
7036                 /* Now we test for 50-move draws. Determine ply count */
7037                 count = forwardMostMove;
7038                 /* look for last irreversble move */
7039                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7040                     count--;
7041                 /* if we hit starting position, add initial plies */
7042                 if( count == backwardMostMove )
7043                     count -= initialRulePlies;
7044                 count = forwardMostMove - count;
7045                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7046                         // adjust reversible move counter for checks in Xiangqi
7047                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7048                         if(i < backwardMostMove) i = backwardMostMove;
7049                         while(i <= forwardMostMove) {
7050                                 lastCheck = inCheck; // check evasion does not count
7051                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7052                                 if(inCheck || lastCheck) count--; // check does not count
7053                                 i++;
7054                         }
7055                 }
7056                 if( count >= 100)
7057                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7058                          /* this is used to judge if draw claims are legal */
7059                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7060                          if(engineOpponent) {
7061                            SendToProgram("force\n", engineOpponent); // suppress reply
7062                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7063                          }
7064                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7065                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7066                          return 1;
7067                 }
7068
7069                 /* if draw offer is pending, treat it as a draw claim
7070                  * when draw condition present, to allow engines a way to
7071                  * claim draws before making their move to avoid a race
7072                  * condition occurring after their move
7073                  */
7074                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7075                          char *p = NULL;
7076                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7077                              p = "Draw claim: 50-move rule";
7078                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7079                              p = "Draw claim: 3-fold repetition";
7080                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7081                              p = "Draw claim: insufficient mating material";
7082                          if( p != NULL && canAdjudicate) {
7083                              if(engineOpponent) {
7084                                SendToProgram("force\n", engineOpponent); // suppress reply
7085                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7086                              }
7087                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7088                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7089                              return 1;
7090                          }
7091                 }
7092
7093                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7094                     if(engineOpponent) {
7095                       SendToProgram("force\n", engineOpponent); // suppress reply
7096                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7097                     }
7098                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7099                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7100                     return 1;
7101                 }
7102         return 0;
7103 }
7104
7105 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7106 {   // [HGM] book: this routine intercepts moves to simulate book replies
7107     char *bookHit = NULL;
7108
7109     //first determine if the incoming move brings opponent into his book
7110     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7111         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7112     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7113     if(bookHit != NULL && !cps->bookSuspend) {
7114         // make sure opponent is not going to reply after receiving move to book position
7115         SendToProgram("force\n", cps);
7116         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7117     }
7118     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7119     // now arrange restart after book miss
7120     if(bookHit) {
7121         // after a book hit we never send 'go', and the code after the call to this routine
7122         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7123         char buf[MSG_SIZ];
7124         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7125         SendToProgram(buf, cps);
7126         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7127     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7128         SendToProgram("go\n", cps);
7129         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7130     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7131         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7132             SendToProgram("go\n", cps);
7133         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7134     }
7135     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7136 }
7137
7138 char *savedMessage;
7139 ChessProgramState *savedState;
7140 void DeferredBookMove(void)
7141 {
7142         if(savedState->lastPing != savedState->lastPong)
7143                     ScheduleDelayedEvent(DeferredBookMove, 10);
7144         else
7145         HandleMachineMove(savedMessage, savedState);
7146 }
7147
7148 void
7149 HandleMachineMove(message, cps)
7150      char *message;
7151      ChessProgramState *cps;
7152 {
7153     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7154     char realname[MSG_SIZ];
7155     int fromX, fromY, toX, toY;
7156     ChessMove moveType;
7157     char promoChar;
7158     char *p;
7159     int machineWhite;
7160     char *bookHit;
7161
7162     cps->userError = 0;
7163
7164 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7165     /*
7166      * Kludge to ignore BEL characters
7167      */
7168     while (*message == '\007') message++;
7169
7170     /*
7171      * [HGM] engine debug message: ignore lines starting with '#' character
7172      */
7173     if(cps->debug && *message == '#') return;
7174
7175     /*
7176      * Look for book output
7177      */
7178     if (cps == &first && bookRequested) {
7179         if (message[0] == '\t' || message[0] == ' ') {
7180             /* Part of the book output is here; append it */
7181             strcat(bookOutput, message);
7182             strcat(bookOutput, "  \n");
7183             return;
7184         } else if (bookOutput[0] != NULLCHAR) {
7185             /* All of book output has arrived; display it */
7186             char *p = bookOutput;
7187             while (*p != NULLCHAR) {
7188                 if (*p == '\t') *p = ' ';
7189                 p++;
7190             }
7191             DisplayInformation(bookOutput);
7192             bookRequested = FALSE;
7193             /* Fall through to parse the current output */
7194         }
7195     }
7196
7197     /*
7198      * Look for machine move.
7199      */
7200     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7201         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7202     {
7203         /* This method is only useful on engines that support ping */
7204         if (cps->lastPing != cps->lastPong) {
7205           if (gameMode == BeginningOfGame) {
7206             /* Extra move from before last new; ignore */
7207             if (appData.debugMode) {
7208                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7209             }
7210           } else {
7211             if (appData.debugMode) {
7212                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7213                         cps->which, gameMode);
7214             }
7215
7216             SendToProgram("undo\n", cps);
7217           }
7218           return;
7219         }
7220
7221         switch (gameMode) {
7222           case BeginningOfGame:
7223             /* Extra move from before last reset; ignore */
7224             if (appData.debugMode) {
7225                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7226             }
7227             return;
7228
7229           case EndOfGame:
7230           case IcsIdle:
7231           default:
7232             /* Extra move after we tried to stop.  The mode test is
7233                not a reliable way of detecting this problem, but it's
7234                the best we can do on engines that don't support ping.
7235             */
7236             if (appData.debugMode) {
7237                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7238                         cps->which, gameMode);
7239             }
7240             SendToProgram("undo\n", cps);
7241             return;
7242
7243           case MachinePlaysWhite:
7244           case IcsPlayingWhite:
7245             machineWhite = TRUE;
7246             break;
7247
7248           case MachinePlaysBlack:
7249           case IcsPlayingBlack:
7250             machineWhite = FALSE;
7251             break;
7252
7253           case TwoMachinesPlay:
7254             machineWhite = (cps->twoMachinesColor[0] == 'w');
7255             break;
7256         }
7257         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7258             if (appData.debugMode) {
7259                 fprintf(debugFP,
7260                         "Ignoring move out of turn by %s, gameMode %d"
7261                         ", forwardMost %d\n",
7262                         cps->which, gameMode, forwardMostMove);
7263             }
7264             return;
7265         }
7266
7267     if (appData.debugMode) { int f = forwardMostMove;
7268         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7269                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7270                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7271     }
7272         if(cps->alphaRank) AlphaRank(machineMove, 4);
7273         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7274                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7275             /* Machine move could not be parsed; ignore it. */
7276           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7277                     machineMove, cps->which);
7278             DisplayError(buf1, 0);
7279             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7280                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7281             if (gameMode == TwoMachinesPlay) {
7282               GameEnds(machineWhite ? BlackWins : WhiteWins,
7283                        buf1, GE_XBOARD);
7284             }
7285             return;
7286         }
7287
7288         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7289         /* So we have to redo legality test with true e.p. status here,  */
7290         /* to make sure an illegal e.p. capture does not slip through,   */
7291         /* to cause a forfeit on a justified illegal-move complaint      */
7292         /* of the opponent.                                              */
7293         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7294            ChessMove moveType;
7295            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7296                              fromY, fromX, toY, toX, promoChar);
7297             if (appData.debugMode) {
7298                 int i;
7299                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7300                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7301                 fprintf(debugFP, "castling rights\n");
7302             }
7303             if(moveType == IllegalMove) {
7304               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7305                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7306                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7307                            buf1, GE_XBOARD);
7308                 return;
7309            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7310            /* [HGM] Kludge to handle engines that send FRC-style castling
7311               when they shouldn't (like TSCP-Gothic) */
7312            switch(moveType) {
7313              case WhiteASideCastleFR:
7314              case BlackASideCastleFR:
7315                toX+=2;
7316                currentMoveString[2]++;
7317                break;
7318              case WhiteHSideCastleFR:
7319              case BlackHSideCastleFR:
7320                toX--;
7321                currentMoveString[2]--;
7322                break;
7323              default: ; // nothing to do, but suppresses warning of pedantic compilers
7324            }
7325         }
7326         hintRequested = FALSE;
7327         lastHint[0] = NULLCHAR;
7328         bookRequested = FALSE;
7329         /* Program may be pondering now */
7330         cps->maybeThinking = TRUE;
7331         if (cps->sendTime == 2) cps->sendTime = 1;
7332         if (cps->offeredDraw) cps->offeredDraw--;
7333
7334         /* currentMoveString is set as a side-effect of ParseOneMove */
7335         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7336         strcat(machineMove, "\n");
7337         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7338
7339         /* [AS] Save move info*/
7340         pvInfoList[ forwardMostMove ].score = programStats.score;
7341         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7342         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7343
7344         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7345
7346         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7347         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7348             int count = 0;
7349
7350             while( count < adjudicateLossPlies ) {
7351                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7352
7353                 if( count & 1 ) {
7354                     score = -score; /* Flip score for winning side */
7355                 }
7356
7357                 if( score > adjudicateLossThreshold ) {
7358                     break;
7359                 }
7360
7361                 count++;
7362             }
7363
7364             if( count >= adjudicateLossPlies ) {
7365                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7366
7367                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7368                     "Xboard adjudication",
7369                     GE_XBOARD );
7370
7371                 return;
7372             }
7373         }
7374
7375         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7376
7377 #if ZIPPY
7378         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7379             first.initDone) {
7380           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7381                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7382                 SendToICS("draw ");
7383                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7384           }
7385           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7386           ics_user_moved = 1;
7387           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7388                 char buf[3*MSG_SIZ];
7389
7390                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7391                         programStats.score / 100.,
7392                         programStats.depth,
7393                         programStats.time / 100.,
7394                         (unsigned int)programStats.nodes,
7395                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7396                         programStats.movelist);
7397                 SendToICS(buf);
7398 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7399           }
7400         }
7401 #endif
7402
7403         /* [AS] Clear stats for next move */
7404         ClearProgramStats();
7405         thinkOutput[0] = NULLCHAR;
7406         hiddenThinkOutputState = 0;
7407
7408         bookHit = NULL;
7409         if (gameMode == TwoMachinesPlay) {
7410             /* [HGM] relaying draw offers moved to after reception of move */
7411             /* and interpreting offer as claim if it brings draw condition */
7412             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7413                 SendToProgram("draw\n", cps->other);
7414             }
7415             if (cps->other->sendTime) {
7416                 SendTimeRemaining(cps->other,
7417                                   cps->other->twoMachinesColor[0] == 'w');
7418             }
7419             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7420             if (firstMove && !bookHit) {
7421                 firstMove = FALSE;
7422                 if (cps->other->useColors) {
7423                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7424                 }
7425                 SendToProgram("go\n", cps->other);
7426             }
7427             cps->other->maybeThinking = TRUE;
7428         }
7429
7430         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7431
7432         if (!pausing && appData.ringBellAfterMoves) {
7433             RingBell();
7434         }
7435
7436         /*
7437          * Reenable menu items that were disabled while
7438          * machine was thinking
7439          */
7440         if (gameMode != TwoMachinesPlay)
7441             SetUserThinkingEnables();
7442
7443         // [HGM] book: after book hit opponent has received move and is now in force mode
7444         // force the book reply into it, and then fake that it outputted this move by jumping
7445         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7446         if(bookHit) {
7447                 static char bookMove[MSG_SIZ]; // a bit generous?
7448
7449                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7450                 strcat(bookMove, bookHit);
7451                 message = bookMove;
7452                 cps = cps->other;
7453                 programStats.nodes = programStats.depth = programStats.time =
7454                 programStats.score = programStats.got_only_move = 0;
7455                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7456
7457                 if(cps->lastPing != cps->lastPong) {
7458                     savedMessage = message; // args for deferred call
7459                     savedState = cps;
7460                     ScheduleDelayedEvent(DeferredBookMove, 10);
7461                     return;
7462                 }
7463                 goto FakeBookMove;
7464         }
7465
7466         return;
7467     }
7468
7469     /* Set special modes for chess engines.  Later something general
7470      *  could be added here; for now there is just one kludge feature,
7471      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7472      *  when "xboard" is given as an interactive command.
7473      */
7474     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7475         cps->useSigint = FALSE;
7476         cps->useSigterm = FALSE;
7477     }
7478     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7479       ParseFeatures(message+8, cps);
7480       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7481     }
7482
7483     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7484       int dummy, s=6; char buf[MSG_SIZ];
7485       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7486       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7487       ParseFEN(boards[0], &dummy, message+s);
7488       DrawPosition(TRUE, boards[0]);
7489       startedFromSetupPosition = TRUE;
7490       return;
7491     }
7492     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7493      * want this, I was asked to put it in, and obliged.
7494      */
7495     if (!strncmp(message, "setboard ", 9)) {
7496         Board initial_position;
7497
7498         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7499
7500         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7501             DisplayError(_("Bad FEN received from engine"), 0);
7502             return ;
7503         } else {
7504            Reset(TRUE, FALSE);
7505            CopyBoard(boards[0], initial_position);
7506            initialRulePlies = FENrulePlies;
7507            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7508            else gameMode = MachinePlaysBlack;
7509            DrawPosition(FALSE, boards[currentMove]);
7510         }
7511         return;
7512     }
7513
7514     /*
7515      * Look for communication commands
7516      */
7517     if (!strncmp(message, "telluser ", 9)) {
7518         if(message[9] == '\\' && message[10] == '\\')
7519             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7520         DisplayNote(message + 9);
7521         return;
7522     }
7523     if (!strncmp(message, "tellusererror ", 14)) {
7524         cps->userError = 1;
7525         if(message[14] == '\\' && message[15] == '\\')
7526             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7527         DisplayError(message + 14, 0);
7528         return;
7529     }
7530     if (!strncmp(message, "tellopponent ", 13)) {
7531       if (appData.icsActive) {
7532         if (loggedOn) {
7533           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7534           SendToICS(buf1);
7535         }
7536       } else {
7537         DisplayNote(message + 13);
7538       }
7539       return;
7540     }
7541     if (!strncmp(message, "tellothers ", 11)) {
7542       if (appData.icsActive) {
7543         if (loggedOn) {
7544           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7545           SendToICS(buf1);
7546         }
7547       }
7548       return;
7549     }
7550     if (!strncmp(message, "tellall ", 8)) {
7551       if (appData.icsActive) {
7552         if (loggedOn) {
7553           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7554           SendToICS(buf1);
7555         }
7556       } else {
7557         DisplayNote(message + 8);
7558       }
7559       return;
7560     }
7561     if (strncmp(message, "warning", 7) == 0) {
7562         /* Undocumented feature, use tellusererror in new code */
7563         DisplayError(message, 0);
7564         return;
7565     }
7566     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7567         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7568         strcat(realname, " query");
7569         AskQuestion(realname, buf2, buf1, cps->pr);
7570         return;
7571     }
7572     /* Commands from the engine directly to ICS.  We don't allow these to be
7573      *  sent until we are logged on. Crafty kibitzes have been known to
7574      *  interfere with the login process.
7575      */
7576     if (loggedOn) {
7577         if (!strncmp(message, "tellics ", 8)) {
7578             SendToICS(message + 8);
7579             SendToICS("\n");
7580             return;
7581         }
7582         if (!strncmp(message, "tellicsnoalias ", 15)) {
7583             SendToICS(ics_prefix);
7584             SendToICS(message + 15);
7585             SendToICS("\n");
7586             return;
7587         }
7588         /* The following are for backward compatibility only */
7589         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7590             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7591             SendToICS(ics_prefix);
7592             SendToICS(message);
7593             SendToICS("\n");
7594             return;
7595         }
7596     }
7597     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7598         return;
7599     }
7600     /*
7601      * If the move is illegal, cancel it and redraw the board.
7602      * Also deal with other error cases.  Matching is rather loose
7603      * here to accommodate engines written before the spec.
7604      */
7605     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7606         strncmp(message, "Error", 5) == 0) {
7607         if (StrStr(message, "name") ||
7608             StrStr(message, "rating") || StrStr(message, "?") ||
7609             StrStr(message, "result") || StrStr(message, "board") ||
7610             StrStr(message, "bk") || StrStr(message, "computer") ||
7611             StrStr(message, "variant") || StrStr(message, "hint") ||
7612             StrStr(message, "random") || StrStr(message, "depth") ||
7613             StrStr(message, "accepted")) {
7614             return;
7615         }
7616         if (StrStr(message, "protover")) {
7617           /* Program is responding to input, so it's apparently done
7618              initializing, and this error message indicates it is
7619              protocol version 1.  So we don't need to wait any longer
7620              for it to initialize and send feature commands. */
7621           FeatureDone(cps, 1);
7622           cps->protocolVersion = 1;
7623           return;
7624         }
7625         cps->maybeThinking = FALSE;
7626
7627         if (StrStr(message, "draw")) {
7628             /* Program doesn't have "draw" command */
7629             cps->sendDrawOffers = 0;
7630             return;
7631         }
7632         if (cps->sendTime != 1 &&
7633             (StrStr(message, "time") || StrStr(message, "otim"))) {
7634           /* Program apparently doesn't have "time" or "otim" command */
7635           cps->sendTime = 0;
7636           return;
7637         }
7638         if (StrStr(message, "analyze")) {
7639             cps->analysisSupport = FALSE;
7640             cps->analyzing = FALSE;
7641             Reset(FALSE, TRUE);
7642             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7643             DisplayError(buf2, 0);
7644             return;
7645         }
7646         if (StrStr(message, "(no matching move)st")) {
7647           /* Special kludge for GNU Chess 4 only */
7648           cps->stKludge = TRUE;
7649           SendTimeControl(cps, movesPerSession, timeControl,
7650                           timeIncrement, appData.searchDepth,
7651                           searchTime);
7652           return;
7653         }
7654         if (StrStr(message, "(no matching move)sd")) {
7655           /* Special kludge for GNU Chess 4 only */
7656           cps->sdKludge = TRUE;
7657           SendTimeControl(cps, movesPerSession, timeControl,
7658                           timeIncrement, appData.searchDepth,
7659                           searchTime);
7660           return;
7661         }
7662         if (!StrStr(message, "llegal")) {
7663             return;
7664         }
7665         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7666             gameMode == IcsIdle) return;
7667         if (forwardMostMove <= backwardMostMove) return;
7668         if (pausing) PauseEvent();
7669       if(appData.forceIllegal) {
7670             // [HGM] illegal: machine refused move; force position after move into it
7671           SendToProgram("force\n", cps);
7672           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7673                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7674                 // when black is to move, while there might be nothing on a2 or black
7675                 // might already have the move. So send the board as if white has the move.
7676                 // But first we must change the stm of the engine, as it refused the last move
7677                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7678                 if(WhiteOnMove(forwardMostMove)) {
7679                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7680                     SendBoard(cps, forwardMostMove); // kludgeless board
7681                 } else {
7682                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7683                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7684                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7685                 }
7686           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7687             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7688                  gameMode == TwoMachinesPlay)
7689               SendToProgram("go\n", cps);
7690             return;
7691       } else
7692         if (gameMode == PlayFromGameFile) {
7693             /* Stop reading this game file */
7694             gameMode = EditGame;
7695             ModeHighlight();
7696         }
7697         currentMove = forwardMostMove-1;
7698         DisplayMove(currentMove-1); /* before DisplayMoveError */
7699         SwitchClocks(forwardMostMove-1); // [HGM] race
7700         DisplayBothClocks();
7701         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7702                 parseList[currentMove], cps->which);
7703         DisplayMoveError(buf1);
7704         DrawPosition(FALSE, boards[currentMove]);
7705
7706         /* [HGM] illegal-move claim should forfeit game when Xboard */
7707         /* only passes fully legal moves                            */
7708         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7709             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7710                                 "False illegal-move claim", GE_XBOARD );
7711         }
7712         return;
7713     }
7714     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7715         /* Program has a broken "time" command that
7716            outputs a string not ending in newline.
7717            Don't use it. */
7718         cps->sendTime = 0;
7719     }
7720
7721     /*
7722      * If chess program startup fails, exit with an error message.
7723      * Attempts to recover here are futile.
7724      */
7725     if ((StrStr(message, "unknown host") != NULL)
7726         || (StrStr(message, "No remote directory") != NULL)
7727         || (StrStr(message, "not found") != NULL)
7728         || (StrStr(message, "No such file") != NULL)
7729         || (StrStr(message, "can't alloc") != NULL)
7730         || (StrStr(message, "Permission denied") != NULL)) {
7731
7732         cps->maybeThinking = FALSE;
7733         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7734                 cps->which, cps->program, cps->host, message);
7735         RemoveInputSource(cps->isr);
7736         DisplayFatalError(buf1, 0, 1);
7737         return;
7738     }
7739
7740     /*
7741      * Look for hint output
7742      */
7743     if (sscanf(message, "Hint: %s", buf1) == 1) {
7744         if (cps == &first && hintRequested) {
7745             hintRequested = FALSE;
7746             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7747                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7748                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7749                                     PosFlags(forwardMostMove),
7750                                     fromY, fromX, toY, toX, promoChar, buf1);
7751                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7752                 DisplayInformation(buf2);
7753             } else {
7754                 /* Hint move could not be parsed!? */
7755               snprintf(buf2, sizeof(buf2),
7756                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7757                         buf1, cps->which);
7758                 DisplayError(buf2, 0);
7759             }
7760         } else {
7761           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7762         }
7763         return;
7764     }
7765
7766     /*
7767      * Ignore other messages if game is not in progress
7768      */
7769     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7770         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7771
7772     /*
7773      * look for win, lose, draw, or draw offer
7774      */
7775     if (strncmp(message, "1-0", 3) == 0) {
7776         char *p, *q, *r = "";
7777         p = strchr(message, '{');
7778         if (p) {
7779             q = strchr(p, '}');
7780             if (q) {
7781                 *q = NULLCHAR;
7782                 r = p + 1;
7783             }
7784         }
7785         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7786         return;
7787     } else if (strncmp(message, "0-1", 3) == 0) {
7788         char *p, *q, *r = "";
7789         p = strchr(message, '{');
7790         if (p) {
7791             q = strchr(p, '}');
7792             if (q) {
7793                 *q = NULLCHAR;
7794                 r = p + 1;
7795             }
7796         }
7797         /* Kludge for Arasan 4.1 bug */
7798         if (strcmp(r, "Black resigns") == 0) {
7799             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7800             return;
7801         }
7802         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7803         return;
7804     } else if (strncmp(message, "1/2", 3) == 0) {
7805         char *p, *q, *r = "";
7806         p = strchr(message, '{');
7807         if (p) {
7808             q = strchr(p, '}');
7809             if (q) {
7810                 *q = NULLCHAR;
7811                 r = p + 1;
7812             }
7813         }
7814
7815         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7816         return;
7817
7818     } else if (strncmp(message, "White resign", 12) == 0) {
7819         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7820         return;
7821     } else if (strncmp(message, "Black resign", 12) == 0) {
7822         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7823         return;
7824     } else if (strncmp(message, "White matches", 13) == 0 ||
7825                strncmp(message, "Black matches", 13) == 0   ) {
7826         /* [HGM] ignore GNUShogi noises */
7827         return;
7828     } else if (strncmp(message, "White", 5) == 0 &&
7829                message[5] != '(' &&
7830                StrStr(message, "Black") == NULL) {
7831         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7832         return;
7833     } else if (strncmp(message, "Black", 5) == 0 &&
7834                message[5] != '(') {
7835         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7836         return;
7837     } else if (strcmp(message, "resign") == 0 ||
7838                strcmp(message, "computer resigns") == 0) {
7839         switch (gameMode) {
7840           case MachinePlaysBlack:
7841           case IcsPlayingBlack:
7842             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7843             break;
7844           case MachinePlaysWhite:
7845           case IcsPlayingWhite:
7846             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7847             break;
7848           case TwoMachinesPlay:
7849             if (cps->twoMachinesColor[0] == 'w')
7850               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7851             else
7852               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7853             break;
7854           default:
7855             /* can't happen */
7856             break;
7857         }
7858         return;
7859     } else if (strncmp(message, "opponent mates", 14) == 0) {
7860         switch (gameMode) {
7861           case MachinePlaysBlack:
7862           case IcsPlayingBlack:
7863             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7864             break;
7865           case MachinePlaysWhite:
7866           case IcsPlayingWhite:
7867             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7868             break;
7869           case TwoMachinesPlay:
7870             if (cps->twoMachinesColor[0] == 'w')
7871               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7872             else
7873               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7874             break;
7875           default:
7876             /* can't happen */
7877             break;
7878         }
7879         return;
7880     } else if (strncmp(message, "computer mates", 14) == 0) {
7881         switch (gameMode) {
7882           case MachinePlaysBlack:
7883           case IcsPlayingBlack:
7884             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7885             break;
7886           case MachinePlaysWhite:
7887           case IcsPlayingWhite:
7888             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7889             break;
7890           case TwoMachinesPlay:
7891             if (cps->twoMachinesColor[0] == 'w')
7892               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7893             else
7894               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7895             break;
7896           default:
7897             /* can't happen */
7898             break;
7899         }
7900         return;
7901     } else if (strncmp(message, "checkmate", 9) == 0) {
7902         if (WhiteOnMove(forwardMostMove)) {
7903             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7904         } else {
7905             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7906         }
7907         return;
7908     } else if (strstr(message, "Draw") != NULL ||
7909                strstr(message, "game is a draw") != NULL) {
7910         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7911         return;
7912     } else if (strstr(message, "offer") != NULL &&
7913                strstr(message, "draw") != NULL) {
7914 #if ZIPPY
7915         if (appData.zippyPlay && first.initDone) {
7916             /* Relay offer to ICS */
7917             SendToICS(ics_prefix);
7918             SendToICS("draw\n");
7919         }
7920 #endif
7921         cps->offeredDraw = 2; /* valid until this engine moves twice */
7922         if (gameMode == TwoMachinesPlay) {
7923             if (cps->other->offeredDraw) {
7924                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7925             /* [HGM] in two-machine mode we delay relaying draw offer      */
7926             /* until after we also have move, to see if it is really claim */
7927             }
7928         } else if (gameMode == MachinePlaysWhite ||
7929                    gameMode == MachinePlaysBlack) {
7930           if (userOfferedDraw) {
7931             DisplayInformation(_("Machine accepts your draw offer"));
7932             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7933           } else {
7934             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7935           }
7936         }
7937     }
7938
7939
7940     /*
7941      * Look for thinking output
7942      */
7943     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7944           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7945                                 ) {
7946         int plylev, mvleft, mvtot, curscore, time;
7947         char mvname[MOVE_LEN];
7948         u64 nodes; // [DM]
7949         char plyext;
7950         int ignore = FALSE;
7951         int prefixHint = FALSE;
7952         mvname[0] = NULLCHAR;
7953
7954         switch (gameMode) {
7955           case MachinePlaysBlack:
7956           case IcsPlayingBlack:
7957             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7958             break;
7959           case MachinePlaysWhite:
7960           case IcsPlayingWhite:
7961             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7962             break;
7963           case AnalyzeMode:
7964           case AnalyzeFile:
7965             break;
7966           case IcsObserving: /* [DM] icsEngineAnalyze */
7967             if (!appData.icsEngineAnalyze) ignore = TRUE;
7968             break;
7969           case TwoMachinesPlay:
7970             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7971                 ignore = TRUE;
7972             }
7973             break;
7974           default:
7975             ignore = TRUE;
7976             break;
7977         }
7978
7979         if (!ignore) {
7980             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7981             buf1[0] = NULLCHAR;
7982             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7983                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7984
7985                 if (plyext != ' ' && plyext != '\t') {
7986                     time *= 100;
7987                 }
7988
7989                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7990                 if( cps->scoreIsAbsolute &&
7991                     ( gameMode == MachinePlaysBlack ||
7992                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7993                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7994                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7995                      !WhiteOnMove(currentMove)
7996                     ) )
7997                 {
7998                     curscore = -curscore;
7999                 }
8000
8001
8002                 tempStats.depth = plylev;
8003                 tempStats.nodes = nodes;
8004                 tempStats.time = time;
8005                 tempStats.score = curscore;
8006                 tempStats.got_only_move = 0;
8007
8008                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8009                         int ticklen;
8010
8011                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8012                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8013                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8014                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8015                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8016                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8017                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8018                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8019                 }
8020
8021                 /* Buffer overflow protection */
8022                 if (buf1[0] != NULLCHAR) {
8023                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8024                         && appData.debugMode) {
8025                         fprintf(debugFP,
8026                                 "PV is too long; using the first %u bytes.\n",
8027                                 (unsigned) sizeof(tempStats.movelist) - 1);
8028                     }
8029
8030                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8031                 } else {
8032                     sprintf(tempStats.movelist, " no PV\n");
8033                 }
8034
8035                 if (tempStats.seen_stat) {
8036                     tempStats.ok_to_send = 1;
8037                 }
8038
8039                 if (strchr(tempStats.movelist, '(') != NULL) {
8040                     tempStats.line_is_book = 1;
8041                     tempStats.nr_moves = 0;
8042                     tempStats.moves_left = 0;
8043                 } else {
8044                     tempStats.line_is_book = 0;
8045                 }
8046
8047                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8048                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8049
8050                 SendProgramStatsToFrontend( cps, &tempStats );
8051
8052                 /*
8053                     [AS] Protect the thinkOutput buffer from overflow... this
8054                     is only useful if buf1 hasn't overflowed first!
8055                 */
8056                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8057                          plylev,
8058                          (gameMode == TwoMachinesPlay ?
8059                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8060                          ((double) curscore) / 100.0,
8061                          prefixHint ? lastHint : "",
8062                          prefixHint ? " " : "" );
8063
8064                 if( buf1[0] != NULLCHAR ) {
8065                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8066
8067                     if( strlen(buf1) > max_len ) {
8068                         if( appData.debugMode) {
8069                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8070                         }
8071                         buf1[max_len+1] = '\0';
8072                     }
8073
8074                     strcat( thinkOutput, buf1 );
8075                 }
8076
8077                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8078                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8079                     DisplayMove(currentMove - 1);
8080                 }
8081                 return;
8082
8083             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8084                 /* crafty (9.25+) says "(only move) <move>"
8085                  * if there is only 1 legal move
8086                  */
8087                 sscanf(p, "(only move) %s", buf1);
8088                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8089                 sprintf(programStats.movelist, "%s (only move)", buf1);
8090                 programStats.depth = 1;
8091                 programStats.nr_moves = 1;
8092                 programStats.moves_left = 1;
8093                 programStats.nodes = 1;
8094                 programStats.time = 1;
8095                 programStats.got_only_move = 1;
8096
8097                 /* Not really, but we also use this member to
8098                    mean "line isn't going to change" (Crafty
8099                    isn't searching, so stats won't change) */
8100                 programStats.line_is_book = 1;
8101
8102                 SendProgramStatsToFrontend( cps, &programStats );
8103
8104                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8105                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8106                     DisplayMove(currentMove - 1);
8107                 }
8108                 return;
8109             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8110                               &time, &nodes, &plylev, &mvleft,
8111                               &mvtot, mvname) >= 5) {
8112                 /* The stat01: line is from Crafty (9.29+) in response
8113                    to the "." command */
8114                 programStats.seen_stat = 1;
8115                 cps->maybeThinking = TRUE;
8116
8117                 if (programStats.got_only_move || !appData.periodicUpdates)
8118                   return;
8119
8120                 programStats.depth = plylev;
8121                 programStats.time = time;
8122                 programStats.nodes = nodes;
8123                 programStats.moves_left = mvleft;
8124                 programStats.nr_moves = mvtot;
8125                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8126                 programStats.ok_to_send = 1;
8127                 programStats.movelist[0] = '\0';
8128
8129                 SendProgramStatsToFrontend( cps, &programStats );
8130
8131                 return;
8132
8133             } else if (strncmp(message,"++",2) == 0) {
8134                 /* Crafty 9.29+ outputs this */
8135                 programStats.got_fail = 2;
8136                 return;
8137
8138             } else if (strncmp(message,"--",2) == 0) {
8139                 /* Crafty 9.29+ outputs this */
8140                 programStats.got_fail = 1;
8141                 return;
8142
8143             } else if (thinkOutput[0] != NULLCHAR &&
8144                        strncmp(message, "    ", 4) == 0) {
8145                 unsigned message_len;
8146
8147                 p = message;
8148                 while (*p && *p == ' ') p++;
8149
8150                 message_len = strlen( p );
8151
8152                 /* [AS] Avoid buffer overflow */
8153                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8154                     strcat(thinkOutput, " ");
8155                     strcat(thinkOutput, p);
8156                 }
8157
8158                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8159                     strcat(programStats.movelist, " ");
8160                     strcat(programStats.movelist, p);
8161                 }
8162
8163                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8164                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8165                     DisplayMove(currentMove - 1);
8166                 }
8167                 return;
8168             }
8169         }
8170         else {
8171             buf1[0] = NULLCHAR;
8172
8173             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8174                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8175             {
8176                 ChessProgramStats cpstats;
8177
8178                 if (plyext != ' ' && plyext != '\t') {
8179                     time *= 100;
8180                 }
8181
8182                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8183                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8184                     curscore = -curscore;
8185                 }
8186
8187                 cpstats.depth = plylev;
8188                 cpstats.nodes = nodes;
8189                 cpstats.time = time;
8190                 cpstats.score = curscore;
8191                 cpstats.got_only_move = 0;
8192                 cpstats.movelist[0] = '\0';
8193
8194                 if (buf1[0] != NULLCHAR) {
8195                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8196                 }
8197
8198                 cpstats.ok_to_send = 0;
8199                 cpstats.line_is_book = 0;
8200                 cpstats.nr_moves = 0;
8201                 cpstats.moves_left = 0;
8202
8203                 SendProgramStatsToFrontend( cps, &cpstats );
8204             }
8205         }
8206     }
8207 }
8208
8209
8210 /* Parse a game score from the character string "game", and
8211    record it as the history of the current game.  The game
8212    score is NOT assumed to start from the standard position.
8213    The display is not updated in any way.
8214    */
8215 void
8216 ParseGameHistory(game)
8217      char *game;
8218 {
8219     ChessMove moveType;
8220     int fromX, fromY, toX, toY, boardIndex;
8221     char promoChar;
8222     char *p, *q;
8223     char buf[MSG_SIZ];
8224
8225     if (appData.debugMode)
8226       fprintf(debugFP, "Parsing game history: %s\n", game);
8227
8228     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8229     gameInfo.site = StrSave(appData.icsHost);
8230     gameInfo.date = PGNDate();
8231     gameInfo.round = StrSave("-");
8232
8233     /* Parse out names of players */
8234     while (*game == ' ') game++;
8235     p = buf;
8236     while (*game != ' ') *p++ = *game++;
8237     *p = NULLCHAR;
8238     gameInfo.white = StrSave(buf);
8239     while (*game == ' ') game++;
8240     p = buf;
8241     while (*game != ' ' && *game != '\n') *p++ = *game++;
8242     *p = NULLCHAR;
8243     gameInfo.black = StrSave(buf);
8244
8245     /* Parse moves */
8246     boardIndex = blackPlaysFirst ? 1 : 0;
8247     yynewstr(game);
8248     for (;;) {
8249         yyboardindex = boardIndex;
8250         moveType = (ChessMove) Myylex();
8251         switch (moveType) {
8252           case IllegalMove:             /* maybe suicide chess, etc. */
8253   if (appData.debugMode) {
8254     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8255     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8256     setbuf(debugFP, NULL);
8257   }
8258           case WhitePromotion:
8259           case BlackPromotion:
8260           case WhiteNonPromotion:
8261           case BlackNonPromotion:
8262           case NormalMove:
8263           case WhiteCapturesEnPassant:
8264           case BlackCapturesEnPassant:
8265           case WhiteKingSideCastle:
8266           case WhiteQueenSideCastle:
8267           case BlackKingSideCastle:
8268           case BlackQueenSideCastle:
8269           case WhiteKingSideCastleWild:
8270           case WhiteQueenSideCastleWild:
8271           case BlackKingSideCastleWild:
8272           case BlackQueenSideCastleWild:
8273           /* PUSH Fabien */
8274           case WhiteHSideCastleFR:
8275           case WhiteASideCastleFR:
8276           case BlackHSideCastleFR:
8277           case BlackASideCastleFR:
8278           /* POP Fabien */
8279             fromX = currentMoveString[0] - AAA;
8280             fromY = currentMoveString[1] - ONE;
8281             toX = currentMoveString[2] - AAA;
8282             toY = currentMoveString[3] - ONE;
8283             promoChar = currentMoveString[4];
8284             break;
8285           case WhiteDrop:
8286           case BlackDrop:
8287             fromX = moveType == WhiteDrop ?
8288               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8289             (int) CharToPiece(ToLower(currentMoveString[0]));
8290             fromY = DROP_RANK;
8291             toX = currentMoveString[2] - AAA;
8292             toY = currentMoveString[3] - ONE;
8293             promoChar = NULLCHAR;
8294             break;
8295           case AmbiguousMove:
8296             /* bug? */
8297             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8298   if (appData.debugMode) {
8299     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8300     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8301     setbuf(debugFP, NULL);
8302   }
8303             DisplayError(buf, 0);
8304             return;
8305           case ImpossibleMove:
8306             /* bug? */
8307             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8308   if (appData.debugMode) {
8309     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8310     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8311     setbuf(debugFP, NULL);
8312   }
8313             DisplayError(buf, 0);
8314             return;
8315           case EndOfFile:
8316             if (boardIndex < backwardMostMove) {
8317                 /* Oops, gap.  How did that happen? */
8318                 DisplayError(_("Gap in move list"), 0);
8319                 return;
8320             }
8321             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8322             if (boardIndex > forwardMostMove) {
8323                 forwardMostMove = boardIndex;
8324             }
8325             return;
8326           case ElapsedTime:
8327             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8328                 strcat(parseList[boardIndex-1], " ");
8329                 strcat(parseList[boardIndex-1], yy_text);
8330             }
8331             continue;
8332           case Comment:
8333           case PGNTag:
8334           case NAG:
8335           default:
8336             /* ignore */
8337             continue;
8338           case WhiteWins:
8339           case BlackWins:
8340           case GameIsDrawn:
8341           case GameUnfinished:
8342             if (gameMode == IcsExamining) {
8343                 if (boardIndex < backwardMostMove) {
8344                     /* Oops, gap.  How did that happen? */
8345                     return;
8346                 }
8347                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8348                 return;
8349             }
8350             gameInfo.result = moveType;
8351             p = strchr(yy_text, '{');
8352             if (p == NULL) p = strchr(yy_text, '(');
8353             if (p == NULL) {
8354                 p = yy_text;
8355                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8356             } else {
8357                 q = strchr(p, *p == '{' ? '}' : ')');
8358                 if (q != NULL) *q = NULLCHAR;
8359                 p++;
8360             }
8361             gameInfo.resultDetails = StrSave(p);
8362             continue;
8363         }
8364         if (boardIndex >= forwardMostMove &&
8365             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8366             backwardMostMove = blackPlaysFirst ? 1 : 0;
8367             return;
8368         }
8369         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8370                                  fromY, fromX, toY, toX, promoChar,
8371                                  parseList[boardIndex]);
8372         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8373         /* currentMoveString is set as a side-effect of yylex */
8374         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8375         strcat(moveList[boardIndex], "\n");
8376         boardIndex++;
8377         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8378         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8379           case MT_NONE:
8380           case MT_STALEMATE:
8381           default:
8382             break;
8383           case MT_CHECK:
8384             if(gameInfo.variant != VariantShogi)
8385                 strcat(parseList[boardIndex - 1], "+");
8386             break;
8387           case MT_CHECKMATE:
8388           case MT_STAINMATE:
8389             strcat(parseList[boardIndex - 1], "#");
8390             break;
8391         }
8392     }
8393 }
8394
8395
8396 /* Apply a move to the given board  */
8397 void
8398 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8399      int fromX, fromY, toX, toY;
8400      int promoChar;
8401      Board board;
8402 {
8403   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8404   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8405
8406     /* [HGM] compute & store e.p. status and castling rights for new position */
8407     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8408
8409       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8410       oldEP = (signed char)board[EP_STATUS];
8411       board[EP_STATUS] = EP_NONE;
8412
8413       if( board[toY][toX] != EmptySquare )
8414            board[EP_STATUS] = EP_CAPTURE;
8415
8416   if (fromY == DROP_RANK) {
8417         /* must be first */
8418         piece = board[toY][toX] = (ChessSquare) fromX;
8419   } else {
8420       int i;
8421
8422       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8423            if( gameInfo.variant == VariantFairy ) board[EP_STATUS] = EP_PAWN_MOVE; // Lance in fairy is Pawn-like
8424       } else
8425       if( board[fromY][fromX] == WhitePawn ) {
8426            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8427                board[EP_STATUS] = EP_PAWN_MOVE;
8428            if( toY-fromY==2) {
8429                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8430                         gameInfo.variant != VariantBerolina || toX < fromX)
8431                       board[EP_STATUS] = toX | berolina;
8432                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8433                         gameInfo.variant != VariantBerolina || toX > fromX)
8434                       board[EP_STATUS] = toX;
8435            }
8436       } else
8437       if( board[fromY][fromX] == BlackPawn ) {
8438            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8439                board[EP_STATUS] = EP_PAWN_MOVE;
8440            if( toY-fromY== -2) {
8441                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8442                         gameInfo.variant != VariantBerolina || toX < fromX)
8443                       board[EP_STATUS] = toX | berolina;
8444                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8445                         gameInfo.variant != VariantBerolina || toX > fromX)
8446                       board[EP_STATUS] = toX;
8447            }
8448        }
8449
8450        for(i=0; i<nrCastlingRights; i++) {
8451            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8452               board[CASTLING][i] == toX   && castlingRank[i] == toY
8453              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8454        }
8455
8456      if (fromX == toX && fromY == toY) return;
8457
8458      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8459      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8460      if(gameInfo.variant == VariantKnightmate)
8461          king += (int) WhiteUnicorn - (int) WhiteKing;
8462
8463     /* Code added by Tord: */
8464     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8465     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8466         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8467       board[fromY][fromX] = EmptySquare;
8468       board[toY][toX] = EmptySquare;
8469       if((toX > fromX) != (piece == WhiteRook)) {
8470         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8471       } else {
8472         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8473       }
8474     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8475                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8476       board[fromY][fromX] = EmptySquare;
8477       board[toY][toX] = EmptySquare;
8478       if((toX > fromX) != (piece == BlackRook)) {
8479         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8480       } else {
8481         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8482       }
8483     /* End of code added by Tord */
8484
8485     } else if (board[fromY][fromX] == king
8486         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8487         && toY == fromY && toX > fromX+1) {
8488         board[fromY][fromX] = EmptySquare;
8489         board[toY][toX] = king;
8490         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8491         board[fromY][BOARD_RGHT-1] = EmptySquare;
8492     } else if (board[fromY][fromX] == king
8493         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8494                && toY == fromY && toX < fromX-1) {
8495         board[fromY][fromX] = EmptySquare;
8496         board[toY][toX] = king;
8497         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8498         board[fromY][BOARD_LEFT] = EmptySquare;
8499     } else if (board[fromY][fromX] == WhitePawn
8500                && toY >= BOARD_HEIGHT-promoRank
8501                && gameInfo.variant != VariantXiangqi
8502                ) {
8503         /* white pawn promotion */
8504         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8505         if (board[toY][toX] == EmptySquare) {
8506             board[toY][toX] = WhiteQueen;
8507         }
8508         if(gameInfo.variant==VariantBughouse ||
8509            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8510             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8511         board[fromY][fromX] = EmptySquare;
8512     } else if ((fromY == BOARD_HEIGHT-4)
8513                && (toX != fromX)
8514                && gameInfo.variant != VariantXiangqi
8515                && gameInfo.variant != VariantBerolina
8516                && (board[fromY][fromX] == WhitePawn)
8517                && (board[toY][toX] == EmptySquare)) {
8518         board[fromY][fromX] = EmptySquare;
8519         board[toY][toX] = WhitePawn;
8520         captured = board[toY - 1][toX];
8521         board[toY - 1][toX] = EmptySquare;
8522     } else if ((fromY == BOARD_HEIGHT-4)
8523                && (toX == fromX)
8524                && gameInfo.variant == VariantBerolina
8525                && (board[fromY][fromX] == WhitePawn)
8526                && (board[toY][toX] == EmptySquare)) {
8527         board[fromY][fromX] = EmptySquare;
8528         board[toY][toX] = WhitePawn;
8529         if(oldEP & EP_BEROLIN_A) {
8530                 captured = board[fromY][fromX-1];
8531                 board[fromY][fromX-1] = EmptySquare;
8532         }else{  captured = board[fromY][fromX+1];
8533                 board[fromY][fromX+1] = EmptySquare;
8534         }
8535     } else if (board[fromY][fromX] == king
8536         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8537                && toY == fromY && toX > fromX+1) {
8538         board[fromY][fromX] = EmptySquare;
8539         board[toY][toX] = king;
8540         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8541         board[fromY][BOARD_RGHT-1] = EmptySquare;
8542     } else if (board[fromY][fromX] == king
8543         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8544                && toY == fromY && toX < fromX-1) {
8545         board[fromY][fromX] = EmptySquare;
8546         board[toY][toX] = king;
8547         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8548         board[fromY][BOARD_LEFT] = EmptySquare;
8549     } else if (fromY == 7 && fromX == 3
8550                && board[fromY][fromX] == BlackKing
8551                && toY == 7 && toX == 5) {
8552         board[fromY][fromX] = EmptySquare;
8553         board[toY][toX] = BlackKing;
8554         board[fromY][7] = EmptySquare;
8555         board[toY][4] = BlackRook;
8556     } else if (fromY == 7 && fromX == 3
8557                && board[fromY][fromX] == BlackKing
8558                && toY == 7 && toX == 1) {
8559         board[fromY][fromX] = EmptySquare;
8560         board[toY][toX] = BlackKing;
8561         board[fromY][0] = EmptySquare;
8562         board[toY][2] = BlackRook;
8563     } else if (board[fromY][fromX] == BlackPawn
8564                && toY < promoRank
8565                && gameInfo.variant != VariantXiangqi
8566                ) {
8567         /* black pawn promotion */
8568         board[toY][toX] = CharToPiece(ToLower(promoChar));
8569         if (board[toY][toX] == EmptySquare) {
8570             board[toY][toX] = BlackQueen;
8571         }
8572         if(gameInfo.variant==VariantBughouse ||
8573            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8574             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8575         board[fromY][fromX] = EmptySquare;
8576     } else if ((fromY == 3)
8577                && (toX != fromX)
8578                && gameInfo.variant != VariantXiangqi
8579                && gameInfo.variant != VariantBerolina
8580                && (board[fromY][fromX] == BlackPawn)
8581                && (board[toY][toX] == EmptySquare)) {
8582         board[fromY][fromX] = EmptySquare;
8583         board[toY][toX] = BlackPawn;
8584         captured = board[toY + 1][toX];
8585         board[toY + 1][toX] = EmptySquare;
8586     } else if ((fromY == 3)
8587                && (toX == fromX)
8588                && gameInfo.variant == VariantBerolina
8589                && (board[fromY][fromX] == BlackPawn)
8590                && (board[toY][toX] == EmptySquare)) {
8591         board[fromY][fromX] = EmptySquare;
8592         board[toY][toX] = BlackPawn;
8593         if(oldEP & EP_BEROLIN_A) {
8594                 captured = board[fromY][fromX-1];
8595                 board[fromY][fromX-1] = EmptySquare;
8596         }else{  captured = board[fromY][fromX+1];
8597                 board[fromY][fromX+1] = EmptySquare;
8598         }
8599     } else {
8600         board[toY][toX] = board[fromY][fromX];
8601         board[fromY][fromX] = EmptySquare;
8602     }
8603   }
8604
8605     if (gameInfo.holdingsWidth != 0) {
8606
8607       /* !!A lot more code needs to be written to support holdings  */
8608       /* [HGM] OK, so I have written it. Holdings are stored in the */
8609       /* penultimate board files, so they are automaticlly stored   */
8610       /* in the game history.                                       */
8611       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8612                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8613         /* Delete from holdings, by decreasing count */
8614         /* and erasing image if necessary            */
8615         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8616         if(p < (int) BlackPawn) { /* white drop */
8617              p -= (int)WhitePawn;
8618                  p = PieceToNumber((ChessSquare)p);
8619              if(p >= gameInfo.holdingsSize) p = 0;
8620              if(--board[p][BOARD_WIDTH-2] <= 0)
8621                   board[p][BOARD_WIDTH-1] = EmptySquare;
8622              if((int)board[p][BOARD_WIDTH-2] < 0)
8623                         board[p][BOARD_WIDTH-2] = 0;
8624         } else {                  /* black drop */
8625              p -= (int)BlackPawn;
8626                  p = PieceToNumber((ChessSquare)p);
8627              if(p >= gameInfo.holdingsSize) p = 0;
8628              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8629                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8630              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8631                         board[BOARD_HEIGHT-1-p][1] = 0;
8632         }
8633       }
8634       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8635           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8636         /* [HGM] holdings: Add to holdings, if holdings exist */
8637         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8638                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8639                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8640         }
8641         p = (int) captured;
8642         if (p >= (int) BlackPawn) {
8643           p -= (int)BlackPawn;
8644           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8645                   /* in Shogi restore piece to its original  first */
8646                   captured = (ChessSquare) (DEMOTED captured);
8647                   p = DEMOTED p;
8648           }
8649           p = PieceToNumber((ChessSquare)p);
8650           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8651           board[p][BOARD_WIDTH-2]++;
8652           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8653         } else {
8654           p -= (int)WhitePawn;
8655           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8656                   captured = (ChessSquare) (DEMOTED captured);
8657                   p = DEMOTED p;
8658           }
8659           p = PieceToNumber((ChessSquare)p);
8660           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8661           board[BOARD_HEIGHT-1-p][1]++;
8662           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8663         }
8664       }
8665     } else if (gameInfo.variant == VariantAtomic) {
8666       if (captured != EmptySquare) {
8667         int y, x;
8668         for (y = toY-1; y <= toY+1; y++) {
8669           for (x = toX-1; x <= toX+1; x++) {
8670             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8671                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8672               board[y][x] = EmptySquare;
8673             }
8674           }
8675         }
8676         board[toY][toX] = EmptySquare;
8677       }
8678     }
8679     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8680         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8681     } else
8682     if(promoChar == '+') {
8683         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8684         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8685     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8686         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8687     }
8688     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8689                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8690         // [HGM] superchess: take promotion piece out of holdings
8691         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8692         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8693             if(!--board[k][BOARD_WIDTH-2])
8694                 board[k][BOARD_WIDTH-1] = EmptySquare;
8695         } else {
8696             if(!--board[BOARD_HEIGHT-1-k][1])
8697                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8698         }
8699     }
8700
8701 }
8702
8703 /* Updates forwardMostMove */
8704 void
8705 MakeMove(fromX, fromY, toX, toY, promoChar)
8706      int fromX, fromY, toX, toY;
8707      int promoChar;
8708 {
8709 //    forwardMostMove++; // [HGM] bare: moved downstream
8710
8711     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8712         int timeLeft; static int lastLoadFlag=0; int king, piece;
8713         piece = boards[forwardMostMove][fromY][fromX];
8714         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8715         if(gameInfo.variant == VariantKnightmate)
8716             king += (int) WhiteUnicorn - (int) WhiteKing;
8717         if(forwardMostMove == 0) {
8718             if(blackPlaysFirst)
8719                 fprintf(serverMoves, "%s;", second.tidy);
8720             fprintf(serverMoves, "%s;", first.tidy);
8721             if(!blackPlaysFirst)
8722                 fprintf(serverMoves, "%s;", second.tidy);
8723         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8724         lastLoadFlag = loadFlag;
8725         // print base move
8726         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8727         // print castling suffix
8728         if( toY == fromY && piece == king ) {
8729             if(toX-fromX > 1)
8730                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8731             if(fromX-toX >1)
8732                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8733         }
8734         // e.p. suffix
8735         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8736              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8737              boards[forwardMostMove][toY][toX] == EmptySquare
8738              && fromX != toX && fromY != toY)
8739                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8740         // promotion suffix
8741         if(promoChar != NULLCHAR)
8742                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8743         if(!loadFlag) {
8744             fprintf(serverMoves, "/%d/%d",
8745                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8746             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8747             else                      timeLeft = blackTimeRemaining/1000;
8748             fprintf(serverMoves, "/%d", timeLeft);
8749         }
8750         fflush(serverMoves);
8751     }
8752
8753     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8754       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8755                         0, 1);
8756       return;
8757     }
8758     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8759     if (commentList[forwardMostMove+1] != NULL) {
8760         free(commentList[forwardMostMove+1]);
8761         commentList[forwardMostMove+1] = NULL;
8762     }
8763     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8764     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8765     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8766     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8767     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8768     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8769     gameInfo.result = GameUnfinished;
8770     if (gameInfo.resultDetails != NULL) {
8771         free(gameInfo.resultDetails);
8772         gameInfo.resultDetails = NULL;
8773     }
8774     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8775                               moveList[forwardMostMove - 1]);
8776     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8777                              PosFlags(forwardMostMove - 1),
8778                              fromY, fromX, toY, toX, promoChar,
8779                              parseList[forwardMostMove - 1]);
8780     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8781       case MT_NONE:
8782       case MT_STALEMATE:
8783       default:
8784         break;
8785       case MT_CHECK:
8786         if(gameInfo.variant != VariantShogi)
8787             strcat(parseList[forwardMostMove - 1], "+");
8788         break;
8789       case MT_CHECKMATE:
8790       case MT_STAINMATE:
8791         strcat(parseList[forwardMostMove - 1], "#");
8792         break;
8793     }
8794     if (appData.debugMode) {
8795         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8796     }
8797
8798 }
8799
8800 /* Updates currentMove if not pausing */
8801 void
8802 ShowMove(fromX, fromY, toX, toY)
8803 {
8804     int instant = (gameMode == PlayFromGameFile) ?
8805         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8806     if(appData.noGUI) return;
8807     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8808         if (!instant) {
8809             if (forwardMostMove == currentMove + 1) {
8810                 AnimateMove(boards[forwardMostMove - 1],
8811                             fromX, fromY, toX, toY);
8812             }
8813             if (appData.highlightLastMove) {
8814                 SetHighlights(fromX, fromY, toX, toY);
8815             }
8816         }
8817         currentMove = forwardMostMove;
8818     }
8819
8820     if (instant) return;
8821
8822     DisplayMove(currentMove - 1);
8823     DrawPosition(FALSE, boards[currentMove]);
8824     DisplayBothClocks();
8825     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8826 }
8827
8828 void SendEgtPath(ChessProgramState *cps)
8829 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8830         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8831
8832         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8833
8834         while(*p) {
8835             char c, *q = name+1, *r, *s;
8836
8837             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8838             while(*p && *p != ',') *q++ = *p++;
8839             *q++ = ':'; *q = 0;
8840             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8841                 strcmp(name, ",nalimov:") == 0 ) {
8842                 // take nalimov path from the menu-changeable option first, if it is defined
8843               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8844                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8845             } else
8846             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8847                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8848                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8849                 s = r = StrStr(s, ":") + 1; // beginning of path info
8850                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8851                 c = *r; *r = 0;             // temporarily null-terminate path info
8852                     *--q = 0;               // strip of trailig ':' from name
8853                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8854                 *r = c;
8855                 SendToProgram(buf,cps);     // send egtbpath command for this format
8856             }
8857             if(*p == ',') p++; // read away comma to position for next format name
8858         }
8859 }
8860
8861 void
8862 InitChessProgram(cps, setup)
8863      ChessProgramState *cps;
8864      int setup; /* [HGM] needed to setup FRC opening position */
8865 {
8866     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8867     if (appData.noChessProgram) return;
8868     hintRequested = FALSE;
8869     bookRequested = FALSE;
8870
8871     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8872     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8873     if(cps->memSize) { /* [HGM] memory */
8874       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8875         SendToProgram(buf, cps);
8876     }
8877     SendEgtPath(cps); /* [HGM] EGT */
8878     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8879       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8880         SendToProgram(buf, cps);
8881     }
8882
8883     SendToProgram(cps->initString, cps);
8884     if (gameInfo.variant != VariantNormal &&
8885         gameInfo.variant != VariantLoadable
8886         /* [HGM] also send variant if board size non-standard */
8887         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8888                                             ) {
8889       char *v = VariantName(gameInfo.variant);
8890       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8891         /* [HGM] in protocol 1 we have to assume all variants valid */
8892         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8893         DisplayFatalError(buf, 0, 1);
8894         return;
8895       }
8896
8897       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8898       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8899       if( gameInfo.variant == VariantXiangqi )
8900            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8901       if( gameInfo.variant == VariantShogi )
8902            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8903       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8904            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8905       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8906                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8907            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8908       if( gameInfo.variant == VariantCourier )
8909            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8910       if( gameInfo.variant == VariantSuper )
8911            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8912       if( gameInfo.variant == VariantGreat )
8913            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8914       if( gameInfo.variant == VariantSChess )
8915            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8916
8917       if(overruled) {
8918         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8919                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8920            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8921            if(StrStr(cps->variants, b) == NULL) {
8922                // specific sized variant not known, check if general sizing allowed
8923                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8924                    if(StrStr(cps->variants, "boardsize") == NULL) {
8925                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8926                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8927                        DisplayFatalError(buf, 0, 1);
8928                        return;
8929                    }
8930                    /* [HGM] here we really should compare with the maximum supported board size */
8931                }
8932            }
8933       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8934       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8935       SendToProgram(buf, cps);
8936     }
8937     currentlyInitializedVariant = gameInfo.variant;
8938
8939     /* [HGM] send opening position in FRC to first engine */
8940     if(setup) {
8941           SendToProgram("force\n", cps);
8942           SendBoard(cps, 0);
8943           /* engine is now in force mode! Set flag to wake it up after first move. */
8944           setboardSpoiledMachineBlack = 1;
8945     }
8946
8947     if (cps->sendICS) {
8948       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8949       SendToProgram(buf, cps);
8950     }
8951     cps->maybeThinking = FALSE;
8952     cps->offeredDraw = 0;
8953     if (!appData.icsActive) {
8954         SendTimeControl(cps, movesPerSession, timeControl,
8955                         timeIncrement, appData.searchDepth,
8956                         searchTime);
8957     }
8958     if (appData.showThinking
8959         // [HGM] thinking: four options require thinking output to be sent
8960         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8961                                 ) {
8962         SendToProgram("post\n", cps);
8963     }
8964     SendToProgram("hard\n", cps);
8965     if (!appData.ponderNextMove) {
8966         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8967            it without being sure what state we are in first.  "hard"
8968            is not a toggle, so that one is OK.
8969          */
8970         SendToProgram("easy\n", cps);
8971     }
8972     if (cps->usePing) {
8973       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8974       SendToProgram(buf, cps);
8975     }
8976     cps->initDone = TRUE;
8977 }
8978
8979
8980 void
8981 StartChessProgram(cps)
8982      ChessProgramState *cps;
8983 {
8984     char buf[MSG_SIZ];
8985     int err;
8986
8987     if (appData.noChessProgram) return;
8988     cps->initDone = FALSE;
8989
8990     if (strcmp(cps->host, "localhost") == 0) {
8991         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8992     } else if (*appData.remoteShell == NULLCHAR) {
8993         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8994     } else {
8995         if (*appData.remoteUser == NULLCHAR) {
8996           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8997                     cps->program);
8998         } else {
8999           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9000                     cps->host, appData.remoteUser, cps->program);
9001         }
9002         err = StartChildProcess(buf, "", &cps->pr);
9003     }
9004
9005     if (err != 0) {
9006       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9007         DisplayFatalError(buf, err, 1);
9008         cps->pr = NoProc;
9009         cps->isr = NULL;
9010         return;
9011     }
9012
9013     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9014     if (cps->protocolVersion > 1) {
9015       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9016       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9017       cps->comboCnt = 0;  //                and values of combo boxes
9018       SendToProgram(buf, cps);
9019     } else {
9020       SendToProgram("xboard\n", cps);
9021     }
9022 }
9023
9024
9025 void
9026 TwoMachinesEventIfReady P((void))
9027 {
9028   if (first.lastPing != first.lastPong) {
9029     DisplayMessage("", _("Waiting for first chess program"));
9030     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9031     return;
9032   }
9033   if (second.lastPing != second.lastPong) {
9034     DisplayMessage("", _("Waiting for second chess program"));
9035     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9036     return;
9037   }
9038   ThawUI();
9039   TwoMachinesEvent();
9040 }
9041
9042 void
9043 NextMatchGame P((void))
9044 {
9045     int index; /* [HGM] autoinc: step load index during match */
9046     Reset(FALSE, TRUE);
9047     if (*appData.loadGameFile != NULLCHAR) {
9048         index = appData.loadGameIndex;
9049         if(index < 0) { // [HGM] autoinc
9050             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9051             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9052         }
9053         LoadGameFromFile(appData.loadGameFile,
9054                          index,
9055                          appData.loadGameFile, FALSE);
9056     } else if (*appData.loadPositionFile != NULLCHAR) {
9057         index = appData.loadPositionIndex;
9058         if(index < 0) { // [HGM] autoinc
9059             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9060             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9061         }
9062         LoadPositionFromFile(appData.loadPositionFile,
9063                              index,
9064                              appData.loadPositionFile);
9065     }
9066     TwoMachinesEventIfReady();
9067 }
9068
9069 void UserAdjudicationEvent( int result )
9070 {
9071     ChessMove gameResult = GameIsDrawn;
9072
9073     if( result > 0 ) {
9074         gameResult = WhiteWins;
9075     }
9076     else if( result < 0 ) {
9077         gameResult = BlackWins;
9078     }
9079
9080     if( gameMode == TwoMachinesPlay ) {
9081         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9082     }
9083 }
9084
9085
9086 // [HGM] save: calculate checksum of game to make games easily identifiable
9087 int StringCheckSum(char *s)
9088 {
9089         int i = 0;
9090         if(s==NULL) return 0;
9091         while(*s) i = i*259 + *s++;
9092         return i;
9093 }
9094
9095 int GameCheckSum()
9096 {
9097         int i, sum=0;
9098         for(i=backwardMostMove; i<forwardMostMove; i++) {
9099                 sum += pvInfoList[i].depth;
9100                 sum += StringCheckSum(parseList[i]);
9101                 sum += StringCheckSum(commentList[i]);
9102                 sum *= 261;
9103         }
9104         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9105         return sum + StringCheckSum(commentList[i]);
9106 } // end of save patch
9107
9108 void
9109 GameEnds(result, resultDetails, whosays)
9110      ChessMove result;
9111      char *resultDetails;
9112      int whosays;
9113 {
9114     GameMode nextGameMode;
9115     int isIcsGame;
9116     char buf[MSG_SIZ], popupRequested = 0;
9117
9118     if(endingGame) return; /* [HGM] crash: forbid recursion */
9119     endingGame = 1;
9120     if(twoBoards) { // [HGM] dual: switch back to one board
9121         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9122         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9123     }
9124     if (appData.debugMode) {
9125       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9126               result, resultDetails ? resultDetails : "(null)", whosays);
9127     }
9128
9129     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9130
9131     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9132         /* If we are playing on ICS, the server decides when the
9133            game is over, but the engine can offer to draw, claim
9134            a draw, or resign.
9135          */
9136 #if ZIPPY
9137         if (appData.zippyPlay && first.initDone) {
9138             if (result == GameIsDrawn) {
9139                 /* In case draw still needs to be claimed */
9140                 SendToICS(ics_prefix);
9141                 SendToICS("draw\n");
9142             } else if (StrCaseStr(resultDetails, "resign")) {
9143                 SendToICS(ics_prefix);
9144                 SendToICS("resign\n");
9145             }
9146         }
9147 #endif
9148         endingGame = 0; /* [HGM] crash */
9149         return;
9150     }
9151
9152     /* If we're loading the game from a file, stop */
9153     if (whosays == GE_FILE) {
9154       (void) StopLoadGameTimer();
9155       gameFileFP = NULL;
9156     }
9157
9158     /* Cancel draw offers */
9159     first.offeredDraw = second.offeredDraw = 0;
9160
9161     /* If this is an ICS game, only ICS can really say it's done;
9162        if not, anyone can. */
9163     isIcsGame = (gameMode == IcsPlayingWhite ||
9164                  gameMode == IcsPlayingBlack ||
9165                  gameMode == IcsObserving    ||
9166                  gameMode == IcsExamining);
9167
9168     if (!isIcsGame || whosays == GE_ICS) {
9169         /* OK -- not an ICS game, or ICS said it was done */
9170         StopClocks();
9171         if (!isIcsGame && !appData.noChessProgram)
9172           SetUserThinkingEnables();
9173
9174         /* [HGM] if a machine claims the game end we verify this claim */
9175         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9176             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9177                 char claimer;
9178                 ChessMove trueResult = (ChessMove) -1;
9179
9180                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9181                                             first.twoMachinesColor[0] :
9182                                             second.twoMachinesColor[0] ;
9183
9184                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9185                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9186                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9187                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9188                 } else
9189                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9190                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9191                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9192                 } else
9193                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9194                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9195                 }
9196
9197                 // now verify win claims, but not in drop games, as we don't understand those yet
9198                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9199                                                  || gameInfo.variant == VariantGreat) &&
9200                     (result == WhiteWins && claimer == 'w' ||
9201                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9202                       if (appData.debugMode) {
9203                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9204                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9205                       }
9206                       if(result != trueResult) {
9207                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9208                               result = claimer == 'w' ? BlackWins : WhiteWins;
9209                               resultDetails = buf;
9210                       }
9211                 } else
9212                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9213                     && (forwardMostMove <= backwardMostMove ||
9214                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9215                         (claimer=='b')==(forwardMostMove&1))
9216                                                                                   ) {
9217                       /* [HGM] verify: draws that were not flagged are false claims */
9218                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9219                       result = claimer == 'w' ? BlackWins : WhiteWins;
9220                       resultDetails = buf;
9221                 }
9222                 /* (Claiming a loss is accepted no questions asked!) */
9223             }
9224             /* [HGM] bare: don't allow bare King to win */
9225             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9226                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9227                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9228                && result != GameIsDrawn)
9229             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9230                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9231                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9232                         if(p >= 0 && p <= (int)WhiteKing) k++;
9233                 }
9234                 if (appData.debugMode) {
9235                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9236                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9237                 }
9238                 if(k <= 1) {
9239                         result = GameIsDrawn;
9240                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9241                         resultDetails = buf;
9242                 }
9243             }
9244         }
9245
9246
9247         if(serverMoves != NULL && !loadFlag) { char c = '=';
9248             if(result==WhiteWins) c = '+';
9249             if(result==BlackWins) c = '-';
9250             if(resultDetails != NULL)
9251                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9252         }
9253         if (resultDetails != NULL) {
9254             gameInfo.result = result;
9255             gameInfo.resultDetails = StrSave(resultDetails);
9256
9257             /* display last move only if game was not loaded from file */
9258             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9259                 DisplayMove(currentMove - 1);
9260
9261             if (forwardMostMove != 0) {
9262                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9263                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9264                                                                 ) {
9265                     if (*appData.saveGameFile != NULLCHAR) {
9266                         SaveGameToFile(appData.saveGameFile, TRUE);
9267                     } else if (appData.autoSaveGames) {
9268                         AutoSaveGame();
9269                     }
9270                     if (*appData.savePositionFile != NULLCHAR) {
9271                         SavePositionToFile(appData.savePositionFile);
9272                     }
9273                 }
9274             }
9275
9276             /* Tell program how game ended in case it is learning */
9277             /* [HGM] Moved this to after saving the PGN, just in case */
9278             /* engine died and we got here through time loss. In that */
9279             /* case we will get a fatal error writing the pipe, which */
9280             /* would otherwise lose us the PGN.                       */
9281             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9282             /* output during GameEnds should never be fatal anymore   */
9283             if (gameMode == MachinePlaysWhite ||
9284                 gameMode == MachinePlaysBlack ||
9285                 gameMode == TwoMachinesPlay ||
9286                 gameMode == IcsPlayingWhite ||
9287                 gameMode == IcsPlayingBlack ||
9288                 gameMode == BeginningOfGame) {
9289                 char buf[MSG_SIZ];
9290                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9291                         resultDetails);
9292                 if (first.pr != NoProc) {
9293                     SendToProgram(buf, &first);
9294                 }
9295                 if (second.pr != NoProc &&
9296                     gameMode == TwoMachinesPlay) {
9297                     SendToProgram(buf, &second);
9298                 }
9299             }
9300         }
9301
9302         if (appData.icsActive) {
9303             if (appData.quietPlay &&
9304                 (gameMode == IcsPlayingWhite ||
9305                  gameMode == IcsPlayingBlack)) {
9306                 SendToICS(ics_prefix);
9307                 SendToICS("set shout 1\n");
9308             }
9309             nextGameMode = IcsIdle;
9310             ics_user_moved = FALSE;
9311             /* clean up premove.  It's ugly when the game has ended and the
9312              * premove highlights are still on the board.
9313              */
9314             if (gotPremove) {
9315               gotPremove = FALSE;
9316               ClearPremoveHighlights();
9317               DrawPosition(FALSE, boards[currentMove]);
9318             }
9319             if (whosays == GE_ICS) {
9320                 switch (result) {
9321                 case WhiteWins:
9322                     if (gameMode == IcsPlayingWhite)
9323                         PlayIcsWinSound();
9324                     else if(gameMode == IcsPlayingBlack)
9325                         PlayIcsLossSound();
9326                     break;
9327                 case BlackWins:
9328                     if (gameMode == IcsPlayingBlack)
9329                         PlayIcsWinSound();
9330                     else if(gameMode == IcsPlayingWhite)
9331                         PlayIcsLossSound();
9332                     break;
9333                 case GameIsDrawn:
9334                     PlayIcsDrawSound();
9335                     break;
9336                 default:
9337                     PlayIcsUnfinishedSound();
9338                 }
9339             }
9340         } else if (gameMode == EditGame ||
9341                    gameMode == PlayFromGameFile ||
9342                    gameMode == AnalyzeMode ||
9343                    gameMode == AnalyzeFile) {
9344             nextGameMode = gameMode;
9345         } else {
9346             nextGameMode = EndOfGame;
9347         }
9348         pausing = FALSE;
9349         ModeHighlight();
9350     } else {
9351         nextGameMode = gameMode;
9352     }
9353
9354     if (appData.noChessProgram) {
9355         gameMode = nextGameMode;
9356         ModeHighlight();
9357         endingGame = 0; /* [HGM] crash */
9358         return;
9359     }
9360
9361     if (first.reuse) {
9362         /* Put first chess program into idle state */
9363         if (first.pr != NoProc &&
9364             (gameMode == MachinePlaysWhite ||
9365              gameMode == MachinePlaysBlack ||
9366              gameMode == TwoMachinesPlay ||
9367              gameMode == IcsPlayingWhite ||
9368              gameMode == IcsPlayingBlack ||
9369              gameMode == BeginningOfGame)) {
9370             SendToProgram("force\n", &first);
9371             if (first.usePing) {
9372               char buf[MSG_SIZ];
9373               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9374               SendToProgram(buf, &first);
9375             }
9376         }
9377     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9378         /* Kill off first chess program */
9379         if (first.isr != NULL)
9380           RemoveInputSource(first.isr);
9381         first.isr = NULL;
9382
9383         if (first.pr != NoProc) {
9384             ExitAnalyzeMode();
9385             DoSleep( appData.delayBeforeQuit );
9386             SendToProgram("quit\n", &first);
9387             DoSleep( appData.delayAfterQuit );
9388             DestroyChildProcess(first.pr, first.useSigterm);
9389         }
9390         first.pr = NoProc;
9391     }
9392     if (second.reuse) {
9393         /* Put second chess program into idle state */
9394         if (second.pr != NoProc &&
9395             gameMode == TwoMachinesPlay) {
9396             SendToProgram("force\n", &second);
9397             if (second.usePing) {
9398               char buf[MSG_SIZ];
9399               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9400               SendToProgram(buf, &second);
9401             }
9402         }
9403     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9404         /* Kill off second chess program */
9405         if (second.isr != NULL)
9406           RemoveInputSource(second.isr);
9407         second.isr = NULL;
9408
9409         if (second.pr != NoProc) {
9410             DoSleep( appData.delayBeforeQuit );
9411             SendToProgram("quit\n", &second);
9412             DoSleep( appData.delayAfterQuit );
9413             DestroyChildProcess(second.pr, second.useSigterm);
9414         }
9415         second.pr = NoProc;
9416     }
9417
9418     if (matchMode && gameMode == TwoMachinesPlay) {
9419         switch (result) {
9420         case WhiteWins:
9421           if (first.twoMachinesColor[0] == 'w') {
9422             first.matchWins++;
9423           } else {
9424             second.matchWins++;
9425           }
9426           break;
9427         case BlackWins:
9428           if (first.twoMachinesColor[0] == 'b') {
9429             first.matchWins++;
9430           } else {
9431             second.matchWins++;
9432           }
9433           break;
9434         default:
9435           break;
9436         }
9437         if (matchGame < appData.matchGames) {
9438             char *tmp;
9439             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9440                 tmp = first.twoMachinesColor;
9441                 first.twoMachinesColor = second.twoMachinesColor;
9442                 second.twoMachinesColor = tmp;
9443             }
9444             gameMode = nextGameMode;
9445             matchGame++;
9446             if(appData.matchPause>10000 || appData.matchPause<10)
9447                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9448             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9449             endingGame = 0; /* [HGM] crash */
9450             return;
9451         } else {
9452             gameMode = nextGameMode;
9453             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9454                      first.tidy, second.tidy,
9455                      first.matchWins, second.matchWins,
9456                      appData.matchGames - (first.matchWins + second.matchWins));
9457             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9458         }
9459     }
9460     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9461         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9462       ExitAnalyzeMode();
9463     gameMode = nextGameMode;
9464     ModeHighlight();
9465     endingGame = 0;  /* [HGM] crash */
9466     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9467       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9468         matchMode = FALSE; appData.matchGames = matchGame = 0;
9469         DisplayNote(buf);
9470       }
9471     }
9472 }
9473
9474 /* Assumes program was just initialized (initString sent).
9475    Leaves program in force mode. */
9476 void
9477 FeedMovesToProgram(cps, upto)
9478      ChessProgramState *cps;
9479      int upto;
9480 {
9481     int i;
9482
9483     if (appData.debugMode)
9484       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9485               startedFromSetupPosition ? "position and " : "",
9486               backwardMostMove, upto, cps->which);
9487     if(currentlyInitializedVariant != gameInfo.variant) {
9488       char buf[MSG_SIZ];
9489         // [HGM] variantswitch: make engine aware of new variant
9490         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9491                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9492         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9493         SendToProgram(buf, cps);
9494         currentlyInitializedVariant = gameInfo.variant;
9495     }
9496     SendToProgram("force\n", cps);
9497     if (startedFromSetupPosition) {
9498         SendBoard(cps, backwardMostMove);
9499     if (appData.debugMode) {
9500         fprintf(debugFP, "feedMoves\n");
9501     }
9502     }
9503     for (i = backwardMostMove; i < upto; i++) {
9504         SendMoveToProgram(i, cps);
9505     }
9506 }
9507
9508
9509 void
9510 ResurrectChessProgram()
9511 {
9512      /* The chess program may have exited.
9513         If so, restart it and feed it all the moves made so far. */
9514
9515     if (appData.noChessProgram || first.pr != NoProc) return;
9516
9517     StartChessProgram(&first);
9518     InitChessProgram(&first, FALSE);
9519     FeedMovesToProgram(&first, currentMove);
9520
9521     if (!first.sendTime) {
9522         /* can't tell gnuchess what its clock should read,
9523            so we bow to its notion. */
9524         ResetClocks();
9525         timeRemaining[0][currentMove] = whiteTimeRemaining;
9526         timeRemaining[1][currentMove] = blackTimeRemaining;
9527     }
9528
9529     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9530                 appData.icsEngineAnalyze) && first.analysisSupport) {
9531       SendToProgram("analyze\n", &first);
9532       first.analyzing = TRUE;
9533     }
9534 }
9535
9536 /*
9537  * Button procedures
9538  */
9539 void
9540 Reset(redraw, init)
9541      int redraw, init;
9542 {
9543     int i;
9544
9545     if (appData.debugMode) {
9546         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9547                 redraw, init, gameMode);
9548     }
9549     CleanupTail(); // [HGM] vari: delete any stored variations
9550     pausing = pauseExamInvalid = FALSE;
9551     startedFromSetupPosition = blackPlaysFirst = FALSE;
9552     firstMove = TRUE;
9553     whiteFlag = blackFlag = FALSE;
9554     userOfferedDraw = FALSE;
9555     hintRequested = bookRequested = FALSE;
9556     first.maybeThinking = FALSE;
9557     second.maybeThinking = FALSE;
9558     first.bookSuspend = FALSE; // [HGM] book
9559     second.bookSuspend = FALSE;
9560     thinkOutput[0] = NULLCHAR;
9561     lastHint[0] = NULLCHAR;
9562     ClearGameInfo(&gameInfo);
9563     gameInfo.variant = StringToVariant(appData.variant);
9564     ics_user_moved = ics_clock_paused = FALSE;
9565     ics_getting_history = H_FALSE;
9566     ics_gamenum = -1;
9567     white_holding[0] = black_holding[0] = NULLCHAR;
9568     ClearProgramStats();
9569     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9570
9571     ResetFrontEnd();
9572     ClearHighlights();
9573     flipView = appData.flipView;
9574     ClearPremoveHighlights();
9575     gotPremove = FALSE;
9576     alarmSounded = FALSE;
9577
9578     GameEnds(EndOfFile, NULL, GE_PLAYER);
9579     if(appData.serverMovesName != NULL) {
9580         /* [HGM] prepare to make moves file for broadcasting */
9581         clock_t t = clock();
9582         if(serverMoves != NULL) fclose(serverMoves);
9583         serverMoves = fopen(appData.serverMovesName, "r");
9584         if(serverMoves != NULL) {
9585             fclose(serverMoves);
9586             /* delay 15 sec before overwriting, so all clients can see end */
9587             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9588         }
9589         serverMoves = fopen(appData.serverMovesName, "w");
9590     }
9591
9592     ExitAnalyzeMode();
9593     gameMode = BeginningOfGame;
9594     ModeHighlight();
9595     if(appData.icsActive) gameInfo.variant = VariantNormal;
9596     currentMove = forwardMostMove = backwardMostMove = 0;
9597     InitPosition(redraw);
9598     for (i = 0; i < MAX_MOVES; i++) {
9599         if (commentList[i] != NULL) {
9600             free(commentList[i]);
9601             commentList[i] = NULL;
9602         }
9603     }
9604     ResetClocks();
9605     timeRemaining[0][0] = whiteTimeRemaining;
9606     timeRemaining[1][0] = blackTimeRemaining;
9607     if (first.pr == NULL) {
9608         StartChessProgram(&first);
9609     }
9610     if (init) {
9611             InitChessProgram(&first, startedFromSetupPosition);
9612     }
9613     DisplayTitle("");
9614     DisplayMessage("", "");
9615     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9616     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9617 }
9618
9619 void
9620 AutoPlayGameLoop()
9621 {
9622     for (;;) {
9623         if (!AutoPlayOneMove())
9624           return;
9625         if (matchMode || appData.timeDelay == 0)
9626           continue;
9627         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9628           return;
9629         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9630         break;
9631     }
9632 }
9633
9634
9635 int
9636 AutoPlayOneMove()
9637 {
9638     int fromX, fromY, toX, toY;
9639
9640     if (appData.debugMode) {
9641       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9642     }
9643
9644     if (gameMode != PlayFromGameFile)
9645       return FALSE;
9646
9647     if (currentMove >= forwardMostMove) {
9648       gameMode = EditGame;
9649       ModeHighlight();
9650
9651       /* [AS] Clear current move marker at the end of a game */
9652       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9653
9654       return FALSE;
9655     }
9656
9657     toX = moveList[currentMove][2] - AAA;
9658     toY = moveList[currentMove][3] - ONE;
9659
9660     if (moveList[currentMove][1] == '@') {
9661         if (appData.highlightLastMove) {
9662             SetHighlights(-1, -1, toX, toY);
9663         }
9664     } else {
9665         fromX = moveList[currentMove][0] - AAA;
9666         fromY = moveList[currentMove][1] - ONE;
9667
9668         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9669
9670         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9671
9672         if (appData.highlightLastMove) {
9673             SetHighlights(fromX, fromY, toX, toY);
9674         }
9675     }
9676     DisplayMove(currentMove);
9677     SendMoveToProgram(currentMove++, &first);
9678     DisplayBothClocks();
9679     DrawPosition(FALSE, boards[currentMove]);
9680     // [HGM] PV info: always display, routine tests if empty
9681     DisplayComment(currentMove - 1, commentList[currentMove]);
9682     return TRUE;
9683 }
9684
9685
9686 int
9687 LoadGameOneMove(readAhead)
9688      ChessMove readAhead;
9689 {
9690     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9691     char promoChar = NULLCHAR;
9692     ChessMove moveType;
9693     char move[MSG_SIZ];
9694     char *p, *q;
9695
9696     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9697         gameMode != AnalyzeMode && gameMode != Training) {
9698         gameFileFP = NULL;
9699         return FALSE;
9700     }
9701
9702     yyboardindex = forwardMostMove;
9703     if (readAhead != EndOfFile) {
9704       moveType = readAhead;
9705     } else {
9706       if (gameFileFP == NULL)
9707           return FALSE;
9708       moveType = (ChessMove) Myylex();
9709     }
9710
9711     done = FALSE;
9712     switch (moveType) {
9713       case Comment:
9714         if (appData.debugMode)
9715           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9716         p = yy_text;
9717
9718         /* append the comment but don't display it */
9719         AppendComment(currentMove, p, FALSE);
9720         return TRUE;
9721
9722       case WhiteCapturesEnPassant:
9723       case BlackCapturesEnPassant:
9724       case WhitePromotion:
9725       case BlackPromotion:
9726       case WhiteNonPromotion:
9727       case BlackNonPromotion:
9728       case NormalMove:
9729       case WhiteKingSideCastle:
9730       case WhiteQueenSideCastle:
9731       case BlackKingSideCastle:
9732       case BlackQueenSideCastle:
9733       case WhiteKingSideCastleWild:
9734       case WhiteQueenSideCastleWild:
9735       case BlackKingSideCastleWild:
9736       case BlackQueenSideCastleWild:
9737       /* PUSH Fabien */
9738       case WhiteHSideCastleFR:
9739       case WhiteASideCastleFR:
9740       case BlackHSideCastleFR:
9741       case BlackASideCastleFR:
9742       /* POP Fabien */
9743         if (appData.debugMode)
9744           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9745         fromX = currentMoveString[0] - AAA;
9746         fromY = currentMoveString[1] - ONE;
9747         toX = currentMoveString[2] - AAA;
9748         toY = currentMoveString[3] - ONE;
9749         promoChar = currentMoveString[4];
9750         break;
9751
9752       case WhiteDrop:
9753       case BlackDrop:
9754         if (appData.debugMode)
9755           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9756         fromX = moveType == WhiteDrop ?
9757           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9758         (int) CharToPiece(ToLower(currentMoveString[0]));
9759         fromY = DROP_RANK;
9760         toX = currentMoveString[2] - AAA;
9761         toY = currentMoveString[3] - ONE;
9762         break;
9763
9764       case WhiteWins:
9765       case BlackWins:
9766       case GameIsDrawn:
9767       case GameUnfinished:
9768         if (appData.debugMode)
9769           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9770         p = strchr(yy_text, '{');
9771         if (p == NULL) p = strchr(yy_text, '(');
9772         if (p == NULL) {
9773             p = yy_text;
9774             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9775         } else {
9776             q = strchr(p, *p == '{' ? '}' : ')');
9777             if (q != NULL) *q = NULLCHAR;
9778             p++;
9779         }
9780         GameEnds(moveType, p, GE_FILE);
9781         done = TRUE;
9782         if (cmailMsgLoaded) {
9783             ClearHighlights();
9784             flipView = WhiteOnMove(currentMove);
9785             if (moveType == GameUnfinished) flipView = !flipView;
9786             if (appData.debugMode)
9787               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9788         }
9789         break;
9790
9791       case EndOfFile:
9792         if (appData.debugMode)
9793           fprintf(debugFP, "Parser hit end of file\n");
9794         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9795           case MT_NONE:
9796           case MT_CHECK:
9797             break;
9798           case MT_CHECKMATE:
9799           case MT_STAINMATE:
9800             if (WhiteOnMove(currentMove)) {
9801                 GameEnds(BlackWins, "Black mates", GE_FILE);
9802             } else {
9803                 GameEnds(WhiteWins, "White mates", GE_FILE);
9804             }
9805             break;
9806           case MT_STALEMATE:
9807             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9808             break;
9809         }
9810         done = TRUE;
9811         break;
9812
9813       case MoveNumberOne:
9814         if (lastLoadGameStart == GNUChessGame) {
9815             /* GNUChessGames have numbers, but they aren't move numbers */
9816             if (appData.debugMode)
9817               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9818                       yy_text, (int) moveType);
9819             return LoadGameOneMove(EndOfFile); /* tail recursion */
9820         }
9821         /* else fall thru */
9822
9823       case XBoardGame:
9824       case GNUChessGame:
9825       case PGNTag:
9826         /* Reached start of next game in file */
9827         if (appData.debugMode)
9828           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9829         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9830           case MT_NONE:
9831           case MT_CHECK:
9832             break;
9833           case MT_CHECKMATE:
9834           case MT_STAINMATE:
9835             if (WhiteOnMove(currentMove)) {
9836                 GameEnds(BlackWins, "Black mates", GE_FILE);
9837             } else {
9838                 GameEnds(WhiteWins, "White mates", GE_FILE);
9839             }
9840             break;
9841           case MT_STALEMATE:
9842             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9843             break;
9844         }
9845         done = TRUE;
9846         break;
9847
9848       case PositionDiagram:     /* should not happen; ignore */
9849       case ElapsedTime:         /* ignore */
9850       case NAG:                 /* ignore */
9851         if (appData.debugMode)
9852           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9853                   yy_text, (int) moveType);
9854         return LoadGameOneMove(EndOfFile); /* tail recursion */
9855
9856       case IllegalMove:
9857         if (appData.testLegality) {
9858             if (appData.debugMode)
9859               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9860             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9861                     (forwardMostMove / 2) + 1,
9862                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9863             DisplayError(move, 0);
9864             done = TRUE;
9865         } else {
9866             if (appData.debugMode)
9867               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9868                       yy_text, currentMoveString);
9869             fromX = currentMoveString[0] - AAA;
9870             fromY = currentMoveString[1] - ONE;
9871             toX = currentMoveString[2] - AAA;
9872             toY = currentMoveString[3] - ONE;
9873             promoChar = currentMoveString[4];
9874         }
9875         break;
9876
9877       case AmbiguousMove:
9878         if (appData.debugMode)
9879           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9880         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9881                 (forwardMostMove / 2) + 1,
9882                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9883         DisplayError(move, 0);
9884         done = TRUE;
9885         break;
9886
9887       default:
9888       case ImpossibleMove:
9889         if (appData.debugMode)
9890           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9891         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9892                 (forwardMostMove / 2) + 1,
9893                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9894         DisplayError(move, 0);
9895         done = TRUE;
9896         break;
9897     }
9898
9899     if (done) {
9900         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9901             DrawPosition(FALSE, boards[currentMove]);
9902             DisplayBothClocks();
9903             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9904               DisplayComment(currentMove - 1, commentList[currentMove]);
9905         }
9906         (void) StopLoadGameTimer();
9907         gameFileFP = NULL;
9908         cmailOldMove = forwardMostMove;
9909         return FALSE;
9910     } else {
9911         /* currentMoveString is set as a side-effect of yylex */
9912         strcat(currentMoveString, "\n");
9913         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9914
9915         thinkOutput[0] = NULLCHAR;
9916         MakeMove(fromX, fromY, toX, toY, promoChar);
9917         currentMove = forwardMostMove;
9918         return TRUE;
9919     }
9920 }
9921
9922 /* Load the nth game from the given file */
9923 int
9924 LoadGameFromFile(filename, n, title, useList)
9925      char *filename;
9926      int n;
9927      char *title;
9928      /*Boolean*/ int useList;
9929 {
9930     FILE *f;
9931     char buf[MSG_SIZ];
9932
9933     if (strcmp(filename, "-") == 0) {
9934         f = stdin;
9935         title = "stdin";
9936     } else {
9937         f = fopen(filename, "rb");
9938         if (f == NULL) {
9939           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9940             DisplayError(buf, errno);
9941             return FALSE;
9942         }
9943     }
9944     if (fseek(f, 0, 0) == -1) {
9945         /* f is not seekable; probably a pipe */
9946         useList = FALSE;
9947     }
9948     if (useList && n == 0) {
9949         int error = GameListBuild(f);
9950         if (error) {
9951             DisplayError(_("Cannot build game list"), error);
9952         } else if (!ListEmpty(&gameList) &&
9953                    ((ListGame *) gameList.tailPred)->number > 1) {
9954             GameListPopUp(f, title);
9955             return TRUE;
9956         }
9957         GameListDestroy();
9958         n = 1;
9959     }
9960     if (n == 0) n = 1;
9961     return LoadGame(f, n, title, FALSE);
9962 }
9963
9964
9965 void
9966 MakeRegisteredMove()
9967 {
9968     int fromX, fromY, toX, toY;
9969     char promoChar;
9970     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9971         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9972           case CMAIL_MOVE:
9973           case CMAIL_DRAW:
9974             if (appData.debugMode)
9975               fprintf(debugFP, "Restoring %s for game %d\n",
9976                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9977
9978             thinkOutput[0] = NULLCHAR;
9979             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9980             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9981             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9982             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9983             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9984             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9985             MakeMove(fromX, fromY, toX, toY, promoChar);
9986             ShowMove(fromX, fromY, toX, toY);
9987
9988             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9989               case MT_NONE:
9990               case MT_CHECK:
9991                 break;
9992
9993               case MT_CHECKMATE:
9994               case MT_STAINMATE:
9995                 if (WhiteOnMove(currentMove)) {
9996                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9997                 } else {
9998                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9999                 }
10000                 break;
10001
10002               case MT_STALEMATE:
10003                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10004                 break;
10005             }
10006
10007             break;
10008
10009           case CMAIL_RESIGN:
10010             if (WhiteOnMove(currentMove)) {
10011                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10012             } else {
10013                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10014             }
10015             break;
10016
10017           case CMAIL_ACCEPT:
10018             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10019             break;
10020
10021           default:
10022             break;
10023         }
10024     }
10025
10026     return;
10027 }
10028
10029 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10030 int
10031 CmailLoadGame(f, gameNumber, title, useList)
10032      FILE *f;
10033      int gameNumber;
10034      char *title;
10035      int useList;
10036 {
10037     int retVal;
10038
10039     if (gameNumber > nCmailGames) {
10040         DisplayError(_("No more games in this message"), 0);
10041         return FALSE;
10042     }
10043     if (f == lastLoadGameFP) {
10044         int offset = gameNumber - lastLoadGameNumber;
10045         if (offset == 0) {
10046             cmailMsg[0] = NULLCHAR;
10047             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10048                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10049                 nCmailMovesRegistered--;
10050             }
10051             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10052             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10053                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10054             }
10055         } else {
10056             if (! RegisterMove()) return FALSE;
10057         }
10058     }
10059
10060     retVal = LoadGame(f, gameNumber, title, useList);
10061
10062     /* Make move registered during previous look at this game, if any */
10063     MakeRegisteredMove();
10064
10065     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10066         commentList[currentMove]
10067           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10068         DisplayComment(currentMove - 1, commentList[currentMove]);
10069     }
10070
10071     return retVal;
10072 }
10073
10074 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10075 int
10076 ReloadGame(offset)
10077      int offset;
10078 {
10079     int gameNumber = lastLoadGameNumber + offset;
10080     if (lastLoadGameFP == NULL) {
10081         DisplayError(_("No game has been loaded yet"), 0);
10082         return FALSE;
10083     }
10084     if (gameNumber <= 0) {
10085         DisplayError(_("Can't back up any further"), 0);
10086         return FALSE;
10087     }
10088     if (cmailMsgLoaded) {
10089         return CmailLoadGame(lastLoadGameFP, gameNumber,
10090                              lastLoadGameTitle, lastLoadGameUseList);
10091     } else {
10092         return LoadGame(lastLoadGameFP, gameNumber,
10093                         lastLoadGameTitle, lastLoadGameUseList);
10094     }
10095 }
10096
10097
10098
10099 /* Load the nth game from open file f */
10100 int
10101 LoadGame(f, gameNumber, title, useList)
10102      FILE *f;
10103      int gameNumber;
10104      char *title;
10105      int useList;
10106 {
10107     ChessMove cm;
10108     char buf[MSG_SIZ];
10109     int gn = gameNumber;
10110     ListGame *lg = NULL;
10111     int numPGNTags = 0;
10112     int err;
10113     GameMode oldGameMode;
10114     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10115
10116     if (appData.debugMode)
10117         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10118
10119     if (gameMode == Training )
10120         SetTrainingModeOff();
10121
10122     oldGameMode = gameMode;
10123     if (gameMode != BeginningOfGame) {
10124       Reset(FALSE, TRUE);
10125     }
10126
10127     gameFileFP = f;
10128     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10129         fclose(lastLoadGameFP);
10130     }
10131
10132     if (useList) {
10133         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10134
10135         if (lg) {
10136             fseek(f, lg->offset, 0);
10137             GameListHighlight(gameNumber);
10138             gn = 1;
10139         }
10140         else {
10141             DisplayError(_("Game number out of range"), 0);
10142             return FALSE;
10143         }
10144     } else {
10145         GameListDestroy();
10146         if (fseek(f, 0, 0) == -1) {
10147             if (f == lastLoadGameFP ?
10148                 gameNumber == lastLoadGameNumber + 1 :
10149                 gameNumber == 1) {
10150                 gn = 1;
10151             } else {
10152                 DisplayError(_("Can't seek on game file"), 0);
10153                 return FALSE;
10154             }
10155         }
10156     }
10157     lastLoadGameFP = f;
10158     lastLoadGameNumber = gameNumber;
10159     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10160     lastLoadGameUseList = useList;
10161
10162     yynewfile(f);
10163
10164     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10165       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10166                 lg->gameInfo.black);
10167             DisplayTitle(buf);
10168     } else if (*title != NULLCHAR) {
10169         if (gameNumber > 1) {
10170           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10171             DisplayTitle(buf);
10172         } else {
10173             DisplayTitle(title);
10174         }
10175     }
10176
10177     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10178         gameMode = PlayFromGameFile;
10179         ModeHighlight();
10180     }
10181
10182     currentMove = forwardMostMove = backwardMostMove = 0;
10183     CopyBoard(boards[0], initialPosition);
10184     StopClocks();
10185
10186     /*
10187      * Skip the first gn-1 games in the file.
10188      * Also skip over anything that precedes an identifiable
10189      * start of game marker, to avoid being confused by
10190      * garbage at the start of the file.  Currently
10191      * recognized start of game markers are the move number "1",
10192      * the pattern "gnuchess .* game", the pattern
10193      * "^[#;%] [^ ]* game file", and a PGN tag block.
10194      * A game that starts with one of the latter two patterns
10195      * will also have a move number 1, possibly
10196      * following a position diagram.
10197      * 5-4-02: Let's try being more lenient and allowing a game to
10198      * start with an unnumbered move.  Does that break anything?
10199      */
10200     cm = lastLoadGameStart = EndOfFile;
10201     while (gn > 0) {
10202         yyboardindex = forwardMostMove;
10203         cm = (ChessMove) Myylex();
10204         switch (cm) {
10205           case EndOfFile:
10206             if (cmailMsgLoaded) {
10207                 nCmailGames = CMAIL_MAX_GAMES - gn;
10208             } else {
10209                 Reset(TRUE, TRUE);
10210                 DisplayError(_("Game not found in file"), 0);
10211             }
10212             return FALSE;
10213
10214           case GNUChessGame:
10215           case XBoardGame:
10216             gn--;
10217             lastLoadGameStart = cm;
10218             break;
10219
10220           case MoveNumberOne:
10221             switch (lastLoadGameStart) {
10222               case GNUChessGame:
10223               case XBoardGame:
10224               case PGNTag:
10225                 break;
10226               case MoveNumberOne:
10227               case EndOfFile:
10228                 gn--;           /* count this game */
10229                 lastLoadGameStart = cm;
10230                 break;
10231               default:
10232                 /* impossible */
10233                 break;
10234             }
10235             break;
10236
10237           case PGNTag:
10238             switch (lastLoadGameStart) {
10239               case GNUChessGame:
10240               case PGNTag:
10241               case MoveNumberOne:
10242               case EndOfFile:
10243                 gn--;           /* count this game */
10244                 lastLoadGameStart = cm;
10245                 break;
10246               case XBoardGame:
10247                 lastLoadGameStart = cm; /* game counted already */
10248                 break;
10249               default:
10250                 /* impossible */
10251                 break;
10252             }
10253             if (gn > 0) {
10254                 do {
10255                     yyboardindex = forwardMostMove;
10256                     cm = (ChessMove) Myylex();
10257                 } while (cm == PGNTag || cm == Comment);
10258             }
10259             break;
10260
10261           case WhiteWins:
10262           case BlackWins:
10263           case GameIsDrawn:
10264             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10265                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10266                     != CMAIL_OLD_RESULT) {
10267                     nCmailResults ++ ;
10268                     cmailResult[  CMAIL_MAX_GAMES
10269                                 - gn - 1] = CMAIL_OLD_RESULT;
10270                 }
10271             }
10272             break;
10273
10274           case NormalMove:
10275             /* Only a NormalMove can be at the start of a game
10276              * without a position diagram. */
10277             if (lastLoadGameStart == EndOfFile ) {
10278               gn--;
10279               lastLoadGameStart = MoveNumberOne;
10280             }
10281             break;
10282
10283           default:
10284             break;
10285         }
10286     }
10287
10288     if (appData.debugMode)
10289       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10290
10291     if (cm == XBoardGame) {
10292         /* Skip any header junk before position diagram and/or move 1 */
10293         for (;;) {
10294             yyboardindex = forwardMostMove;
10295             cm = (ChessMove) Myylex();
10296
10297             if (cm == EndOfFile ||
10298                 cm == GNUChessGame || cm == XBoardGame) {
10299                 /* Empty game; pretend end-of-file and handle later */
10300                 cm = EndOfFile;
10301                 break;
10302             }
10303
10304             if (cm == MoveNumberOne || cm == PositionDiagram ||
10305                 cm == PGNTag || cm == Comment)
10306               break;
10307         }
10308     } else if (cm == GNUChessGame) {
10309         if (gameInfo.event != NULL) {
10310             free(gameInfo.event);
10311         }
10312         gameInfo.event = StrSave(yy_text);
10313     }
10314
10315     startedFromSetupPosition = FALSE;
10316     while (cm == PGNTag) {
10317         if (appData.debugMode)
10318           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10319         err = ParsePGNTag(yy_text, &gameInfo);
10320         if (!err) numPGNTags++;
10321
10322         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10323         if(gameInfo.variant != oldVariant) {
10324             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10325             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10326             InitPosition(TRUE);
10327             oldVariant = gameInfo.variant;
10328             if (appData.debugMode)
10329               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10330         }
10331
10332
10333         if (gameInfo.fen != NULL) {
10334           Board initial_position;
10335           startedFromSetupPosition = TRUE;
10336           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10337             Reset(TRUE, TRUE);
10338             DisplayError(_("Bad FEN position in file"), 0);
10339             return FALSE;
10340           }
10341           CopyBoard(boards[0], initial_position);
10342           if (blackPlaysFirst) {
10343             currentMove = forwardMostMove = backwardMostMove = 1;
10344             CopyBoard(boards[1], initial_position);
10345             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10346             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10347             timeRemaining[0][1] = whiteTimeRemaining;
10348             timeRemaining[1][1] = blackTimeRemaining;
10349             if (commentList[0] != NULL) {
10350               commentList[1] = commentList[0];
10351               commentList[0] = NULL;
10352             }
10353           } else {
10354             currentMove = forwardMostMove = backwardMostMove = 0;
10355           }
10356           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10357           {   int i;
10358               initialRulePlies = FENrulePlies;
10359               for( i=0; i< nrCastlingRights; i++ )
10360                   initialRights[i] = initial_position[CASTLING][i];
10361           }
10362           yyboardindex = forwardMostMove;
10363           free(gameInfo.fen);
10364           gameInfo.fen = NULL;
10365         }
10366
10367         yyboardindex = forwardMostMove;
10368         cm = (ChessMove) Myylex();
10369
10370         /* Handle comments interspersed among the tags */
10371         while (cm == Comment) {
10372             char *p;
10373             if (appData.debugMode)
10374               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10375             p = yy_text;
10376             AppendComment(currentMove, p, FALSE);
10377             yyboardindex = forwardMostMove;
10378             cm = (ChessMove) Myylex();
10379         }
10380     }
10381
10382     /* don't rely on existence of Event tag since if game was
10383      * pasted from clipboard the Event tag may not exist
10384      */
10385     if (numPGNTags > 0){
10386         char *tags;
10387         if (gameInfo.variant == VariantNormal) {
10388           VariantClass v = StringToVariant(gameInfo.event);
10389           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10390           if(v < VariantShogi) gameInfo.variant = v;
10391         }
10392         if (!matchMode) {
10393           if( appData.autoDisplayTags ) {
10394             tags = PGNTags(&gameInfo);
10395             TagsPopUp(tags, CmailMsg());
10396             free(tags);
10397           }
10398         }
10399     } else {
10400         /* Make something up, but don't display it now */
10401         SetGameInfo();
10402         TagsPopDown();
10403     }
10404
10405     if (cm == PositionDiagram) {
10406         int i, j;
10407         char *p;
10408         Board initial_position;
10409
10410         if (appData.debugMode)
10411           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10412
10413         if (!startedFromSetupPosition) {
10414             p = yy_text;
10415             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10416               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10417                 switch (*p) {
10418                   case '[':
10419                   case '-':
10420                   case ' ':
10421                   case '\t':
10422                   case '\n':
10423                   case '\r':
10424                     break;
10425                   default:
10426                     initial_position[i][j++] = CharToPiece(*p);
10427                     break;
10428                 }
10429             while (*p == ' ' || *p == '\t' ||
10430                    *p == '\n' || *p == '\r') p++;
10431
10432             if (strncmp(p, "black", strlen("black"))==0)
10433               blackPlaysFirst = TRUE;
10434             else
10435               blackPlaysFirst = FALSE;
10436             startedFromSetupPosition = TRUE;
10437
10438             CopyBoard(boards[0], initial_position);
10439             if (blackPlaysFirst) {
10440                 currentMove = forwardMostMove = backwardMostMove = 1;
10441                 CopyBoard(boards[1], initial_position);
10442                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10443                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10444                 timeRemaining[0][1] = whiteTimeRemaining;
10445                 timeRemaining[1][1] = blackTimeRemaining;
10446                 if (commentList[0] != NULL) {
10447                     commentList[1] = commentList[0];
10448                     commentList[0] = NULL;
10449                 }
10450             } else {
10451                 currentMove = forwardMostMove = backwardMostMove = 0;
10452             }
10453         }
10454         yyboardindex = forwardMostMove;
10455         cm = (ChessMove) Myylex();
10456     }
10457
10458     if (first.pr == NoProc) {
10459         StartChessProgram(&first);
10460     }
10461     InitChessProgram(&first, FALSE);
10462     SendToProgram("force\n", &first);
10463     if (startedFromSetupPosition) {
10464         SendBoard(&first, forwardMostMove);
10465     if (appData.debugMode) {
10466         fprintf(debugFP, "Load Game\n");
10467     }
10468         DisplayBothClocks();
10469     }
10470
10471     /* [HGM] server: flag to write setup moves in broadcast file as one */
10472     loadFlag = appData.suppressLoadMoves;
10473
10474     while (cm == Comment) {
10475         char *p;
10476         if (appData.debugMode)
10477           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10478         p = yy_text;
10479         AppendComment(currentMove, p, FALSE);
10480         yyboardindex = forwardMostMove;
10481         cm = (ChessMove) Myylex();
10482     }
10483
10484     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10485         cm == WhiteWins || cm == BlackWins ||
10486         cm == GameIsDrawn || cm == GameUnfinished) {
10487         DisplayMessage("", _("No moves in game"));
10488         if (cmailMsgLoaded) {
10489             if (appData.debugMode)
10490               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10491             ClearHighlights();
10492             flipView = FALSE;
10493         }
10494         DrawPosition(FALSE, boards[currentMove]);
10495         DisplayBothClocks();
10496         gameMode = EditGame;
10497         ModeHighlight();
10498         gameFileFP = NULL;
10499         cmailOldMove = 0;
10500         return TRUE;
10501     }
10502
10503     // [HGM] PV info: routine tests if comment empty
10504     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10505         DisplayComment(currentMove - 1, commentList[currentMove]);
10506     }
10507     if (!matchMode && appData.timeDelay != 0)
10508       DrawPosition(FALSE, boards[currentMove]);
10509
10510     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10511       programStats.ok_to_send = 1;
10512     }
10513
10514     /* if the first token after the PGN tags is a move
10515      * and not move number 1, retrieve it from the parser
10516      */
10517     if (cm != MoveNumberOne)
10518         LoadGameOneMove(cm);
10519
10520     /* load the remaining moves from the file */
10521     while (LoadGameOneMove(EndOfFile)) {
10522       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10523       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10524     }
10525
10526     /* rewind to the start of the game */
10527     currentMove = backwardMostMove;
10528
10529     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10530
10531     if (oldGameMode == AnalyzeFile ||
10532         oldGameMode == AnalyzeMode) {
10533       AnalyzeFileEvent();
10534     }
10535
10536     if (matchMode || appData.timeDelay == 0) {
10537       ToEndEvent();
10538       gameMode = EditGame;
10539       ModeHighlight();
10540     } else if (appData.timeDelay > 0) {
10541       AutoPlayGameLoop();
10542     }
10543
10544     if (appData.debugMode)
10545         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10546
10547     loadFlag = 0; /* [HGM] true game starts */
10548     return TRUE;
10549 }
10550
10551 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10552 int
10553 ReloadPosition(offset)
10554      int offset;
10555 {
10556     int positionNumber = lastLoadPositionNumber + offset;
10557     if (lastLoadPositionFP == NULL) {
10558         DisplayError(_("No position has been loaded yet"), 0);
10559         return FALSE;
10560     }
10561     if (positionNumber <= 0) {
10562         DisplayError(_("Can't back up any further"), 0);
10563         return FALSE;
10564     }
10565     return LoadPosition(lastLoadPositionFP, positionNumber,
10566                         lastLoadPositionTitle);
10567 }
10568
10569 /* Load the nth position from the given file */
10570 int
10571 LoadPositionFromFile(filename, n, title)
10572      char *filename;
10573      int n;
10574      char *title;
10575 {
10576     FILE *f;
10577     char buf[MSG_SIZ];
10578
10579     if (strcmp(filename, "-") == 0) {
10580         return LoadPosition(stdin, n, "stdin");
10581     } else {
10582         f = fopen(filename, "rb");
10583         if (f == NULL) {
10584             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10585             DisplayError(buf, errno);
10586             return FALSE;
10587         } else {
10588             return LoadPosition(f, n, title);
10589         }
10590     }
10591 }
10592
10593 /* Load the nth position from the given open file, and close it */
10594 int
10595 LoadPosition(f, positionNumber, title)
10596      FILE *f;
10597      int positionNumber;
10598      char *title;
10599 {
10600     char *p, line[MSG_SIZ];
10601     Board initial_position;
10602     int i, j, fenMode, pn;
10603
10604     if (gameMode == Training )
10605         SetTrainingModeOff();
10606
10607     if (gameMode != BeginningOfGame) {
10608         Reset(FALSE, TRUE);
10609     }
10610     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10611         fclose(lastLoadPositionFP);
10612     }
10613     if (positionNumber == 0) positionNumber = 1;
10614     lastLoadPositionFP = f;
10615     lastLoadPositionNumber = positionNumber;
10616     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10617     if (first.pr == NoProc) {
10618       StartChessProgram(&first);
10619       InitChessProgram(&first, FALSE);
10620     }
10621     pn = positionNumber;
10622     if (positionNumber < 0) {
10623         /* Negative position number means to seek to that byte offset */
10624         if (fseek(f, -positionNumber, 0) == -1) {
10625             DisplayError(_("Can't seek on position file"), 0);
10626             return FALSE;
10627         };
10628         pn = 1;
10629     } else {
10630         if (fseek(f, 0, 0) == -1) {
10631             if (f == lastLoadPositionFP ?
10632                 positionNumber == lastLoadPositionNumber + 1 :
10633                 positionNumber == 1) {
10634                 pn = 1;
10635             } else {
10636                 DisplayError(_("Can't seek on position file"), 0);
10637                 return FALSE;
10638             }
10639         }
10640     }
10641     /* See if this file is FEN or old-style xboard */
10642     if (fgets(line, MSG_SIZ, f) == NULL) {
10643         DisplayError(_("Position not found in file"), 0);
10644         return FALSE;
10645     }
10646     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10647     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10648
10649     if (pn >= 2) {
10650         if (fenMode || line[0] == '#') pn--;
10651         while (pn > 0) {
10652             /* skip positions before number pn */
10653             if (fgets(line, MSG_SIZ, f) == NULL) {
10654                 Reset(TRUE, TRUE);
10655                 DisplayError(_("Position not found in file"), 0);
10656                 return FALSE;
10657             }
10658             if (fenMode || line[0] == '#') pn--;
10659         }
10660     }
10661
10662     if (fenMode) {
10663         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10664             DisplayError(_("Bad FEN position in file"), 0);
10665             return FALSE;
10666         }
10667     } else {
10668         (void) fgets(line, MSG_SIZ, f);
10669         (void) fgets(line, MSG_SIZ, f);
10670
10671         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10672             (void) fgets(line, MSG_SIZ, f);
10673             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10674                 if (*p == ' ')
10675                   continue;
10676                 initial_position[i][j++] = CharToPiece(*p);
10677             }
10678         }
10679
10680         blackPlaysFirst = FALSE;
10681         if (!feof(f)) {
10682             (void) fgets(line, MSG_SIZ, f);
10683             if (strncmp(line, "black", strlen("black"))==0)
10684               blackPlaysFirst = TRUE;
10685         }
10686     }
10687     startedFromSetupPosition = TRUE;
10688
10689     SendToProgram("force\n", &first);
10690     CopyBoard(boards[0], initial_position);
10691     if (blackPlaysFirst) {
10692         currentMove = forwardMostMove = backwardMostMove = 1;
10693         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10694         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10695         CopyBoard(boards[1], initial_position);
10696         DisplayMessage("", _("Black to play"));
10697     } else {
10698         currentMove = forwardMostMove = backwardMostMove = 0;
10699         DisplayMessage("", _("White to play"));
10700     }
10701     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10702     SendBoard(&first, forwardMostMove);
10703     if (appData.debugMode) {
10704 int i, j;
10705   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10706   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10707         fprintf(debugFP, "Load Position\n");
10708     }
10709
10710     if (positionNumber > 1) {
10711       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10712         DisplayTitle(line);
10713     } else {
10714         DisplayTitle(title);
10715     }
10716     gameMode = EditGame;
10717     ModeHighlight();
10718     ResetClocks();
10719     timeRemaining[0][1] = whiteTimeRemaining;
10720     timeRemaining[1][1] = blackTimeRemaining;
10721     DrawPosition(FALSE, boards[currentMove]);
10722
10723     return TRUE;
10724 }
10725
10726
10727 void
10728 CopyPlayerNameIntoFileName(dest, src)
10729      char **dest, *src;
10730 {
10731     while (*src != NULLCHAR && *src != ',') {
10732         if (*src == ' ') {
10733             *(*dest)++ = '_';
10734             src++;
10735         } else {
10736             *(*dest)++ = *src++;
10737         }
10738     }
10739 }
10740
10741 char *DefaultFileName(ext)
10742      char *ext;
10743 {
10744     static char def[MSG_SIZ];
10745     char *p;
10746
10747     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10748         p = def;
10749         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10750         *p++ = '-';
10751         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10752         *p++ = '.';
10753         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10754     } else {
10755         def[0] = NULLCHAR;
10756     }
10757     return def;
10758 }
10759
10760 /* Save the current game to the given file */
10761 int
10762 SaveGameToFile(filename, append)
10763      char *filename;
10764      int append;
10765 {
10766     FILE *f;
10767     char buf[MSG_SIZ];
10768
10769     if (strcmp(filename, "-") == 0) {
10770         return SaveGame(stdout, 0, NULL);
10771     } else {
10772         f = fopen(filename, append ? "a" : "w");
10773         if (f == NULL) {
10774             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10775             DisplayError(buf, errno);
10776             return FALSE;
10777         } else {
10778             return SaveGame(f, 0, NULL);
10779         }
10780     }
10781 }
10782
10783 char *
10784 SavePart(str)
10785      char *str;
10786 {
10787     static char buf[MSG_SIZ];
10788     char *p;
10789
10790     p = strchr(str, ' ');
10791     if (p == NULL) return str;
10792     strncpy(buf, str, p - str);
10793     buf[p - str] = NULLCHAR;
10794     return buf;
10795 }
10796
10797 #define PGN_MAX_LINE 75
10798
10799 #define PGN_SIDE_WHITE  0
10800 #define PGN_SIDE_BLACK  1
10801
10802 /* [AS] */
10803 static int FindFirstMoveOutOfBook( int side )
10804 {
10805     int result = -1;
10806
10807     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10808         int index = backwardMostMove;
10809         int has_book_hit = 0;
10810
10811         if( (index % 2) != side ) {
10812             index++;
10813         }
10814
10815         while( index < forwardMostMove ) {
10816             /* Check to see if engine is in book */
10817             int depth = pvInfoList[index].depth;
10818             int score = pvInfoList[index].score;
10819             int in_book = 0;
10820
10821             if( depth <= 2 ) {
10822                 in_book = 1;
10823             }
10824             else if( score == 0 && depth == 63 ) {
10825                 in_book = 1; /* Zappa */
10826             }
10827             else if( score == 2 && depth == 99 ) {
10828                 in_book = 1; /* Abrok */
10829             }
10830
10831             has_book_hit += in_book;
10832
10833             if( ! in_book ) {
10834                 result = index;
10835
10836                 break;
10837             }
10838
10839             index += 2;
10840         }
10841     }
10842
10843     return result;
10844 }
10845
10846 /* [AS] */
10847 void GetOutOfBookInfo( char * buf )
10848 {
10849     int oob[2];
10850     int i;
10851     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10852
10853     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10854     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10855
10856     *buf = '\0';
10857
10858     if( oob[0] >= 0 || oob[1] >= 0 ) {
10859         for( i=0; i<2; i++ ) {
10860             int idx = oob[i];
10861
10862             if( idx >= 0 ) {
10863                 if( i > 0 && oob[0] >= 0 ) {
10864                     strcat( buf, "   " );
10865                 }
10866
10867                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10868                 sprintf( buf+strlen(buf), "%s%.2f",
10869                     pvInfoList[idx].score >= 0 ? "+" : "",
10870                     pvInfoList[idx].score / 100.0 );
10871             }
10872         }
10873     }
10874 }
10875
10876 /* Save game in PGN style and close the file */
10877 int
10878 SaveGamePGN(f)
10879      FILE *f;
10880 {
10881     int i, offset, linelen, newblock;
10882     time_t tm;
10883 //    char *movetext;
10884     char numtext[32];
10885     int movelen, numlen, blank;
10886     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10887
10888     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10889
10890     tm = time((time_t *) NULL);
10891
10892     PrintPGNTags(f, &gameInfo);
10893
10894     if (backwardMostMove > 0 || startedFromSetupPosition) {
10895         char *fen = PositionToFEN(backwardMostMove, NULL);
10896         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10897         fprintf(f, "\n{--------------\n");
10898         PrintPosition(f, backwardMostMove);
10899         fprintf(f, "--------------}\n");
10900         free(fen);
10901     }
10902     else {
10903         /* [AS] Out of book annotation */
10904         if( appData.saveOutOfBookInfo ) {
10905             char buf[64];
10906
10907             GetOutOfBookInfo( buf );
10908
10909             if( buf[0] != '\0' ) {
10910                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10911             }
10912         }
10913
10914         fprintf(f, "\n");
10915     }
10916
10917     i = backwardMostMove;
10918     linelen = 0;
10919     newblock = TRUE;
10920
10921     while (i < forwardMostMove) {
10922         /* Print comments preceding this move */
10923         if (commentList[i] != NULL) {
10924             if (linelen > 0) fprintf(f, "\n");
10925             fprintf(f, "%s", commentList[i]);
10926             linelen = 0;
10927             newblock = TRUE;
10928         }
10929
10930         /* Format move number */
10931         if ((i % 2) == 0)
10932           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10933         else
10934           if (newblock)
10935             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10936           else
10937             numtext[0] = NULLCHAR;
10938
10939         numlen = strlen(numtext);
10940         newblock = FALSE;
10941
10942         /* Print move number */
10943         blank = linelen > 0 && numlen > 0;
10944         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10945             fprintf(f, "\n");
10946             linelen = 0;
10947             blank = 0;
10948         }
10949         if (blank) {
10950             fprintf(f, " ");
10951             linelen++;
10952         }
10953         fprintf(f, "%s", numtext);
10954         linelen += numlen;
10955
10956         /* Get move */
10957         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10958         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10959
10960         /* Print move */
10961         blank = linelen > 0 && movelen > 0;
10962         if (linelen + (blank ? 1 : 0) + movelen > 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", move_buffer);
10972         linelen += movelen;
10973
10974         /* [AS] Add PV info if present */
10975         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10976             /* [HGM] add time */
10977             char buf[MSG_SIZ]; int seconds;
10978
10979             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10980
10981             if( seconds <= 0)
10982               buf[0] = 0;
10983             else
10984               if( seconds < 30 )
10985                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10986               else
10987                 {
10988                   seconds = (seconds + 4)/10; // round to full seconds
10989                   if( seconds < 60 )
10990                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10991                   else
10992                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10993                 }
10994
10995             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10996                       pvInfoList[i].score >= 0 ? "+" : "",
10997                       pvInfoList[i].score / 100.0,
10998                       pvInfoList[i].depth,
10999                       buf );
11000
11001             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11002
11003             /* Print score/depth */
11004             blank = linelen > 0 && movelen > 0;
11005             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11006                 fprintf(f, "\n");
11007                 linelen = 0;
11008                 blank = 0;
11009             }
11010             if (blank) {
11011                 fprintf(f, " ");
11012                 linelen++;
11013             }
11014             fprintf(f, "%s", move_buffer);
11015             linelen += movelen;
11016         }
11017
11018         i++;
11019     }
11020
11021     /* Start a new line */
11022     if (linelen > 0) fprintf(f, "\n");
11023
11024     /* Print comments after last move */
11025     if (commentList[i] != NULL) {
11026         fprintf(f, "%s\n", commentList[i]);
11027     }
11028
11029     /* Print result */
11030     if (gameInfo.resultDetails != NULL &&
11031         gameInfo.resultDetails[0] != NULLCHAR) {
11032         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11033                 PGNResult(gameInfo.result));
11034     } else {
11035         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11036     }
11037
11038     fclose(f);
11039     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11040     return TRUE;
11041 }
11042
11043 /* Save game in old style and close the file */
11044 int
11045 SaveGameOldStyle(f)
11046      FILE *f;
11047 {
11048     int i, offset;
11049     time_t tm;
11050
11051     tm = time((time_t *) NULL);
11052
11053     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11054     PrintOpponents(f);
11055
11056     if (backwardMostMove > 0 || startedFromSetupPosition) {
11057         fprintf(f, "\n[--------------\n");
11058         PrintPosition(f, backwardMostMove);
11059         fprintf(f, "--------------]\n");
11060     } else {
11061         fprintf(f, "\n");
11062     }
11063
11064     i = backwardMostMove;
11065     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11066
11067     while (i < forwardMostMove) {
11068         if (commentList[i] != NULL) {
11069             fprintf(f, "[%s]\n", commentList[i]);
11070         }
11071
11072         if ((i % 2) == 1) {
11073             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11074             i++;
11075         } else {
11076             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11077             i++;
11078             if (commentList[i] != NULL) {
11079                 fprintf(f, "\n");
11080                 continue;
11081             }
11082             if (i >= forwardMostMove) {
11083                 fprintf(f, "\n");
11084                 break;
11085             }
11086             fprintf(f, "%s\n", parseList[i]);
11087             i++;
11088         }
11089     }
11090
11091     if (commentList[i] != NULL) {
11092         fprintf(f, "[%s]\n", commentList[i]);
11093     }
11094
11095     /* This isn't really the old style, but it's close enough */
11096     if (gameInfo.resultDetails != NULL &&
11097         gameInfo.resultDetails[0] != NULLCHAR) {
11098         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11099                 gameInfo.resultDetails);
11100     } else {
11101         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11102     }
11103
11104     fclose(f);
11105     return TRUE;
11106 }
11107
11108 /* Save the current game to open file f and close the file */
11109 int
11110 SaveGame(f, dummy, dummy2)
11111      FILE *f;
11112      int dummy;
11113      char *dummy2;
11114 {
11115     if (gameMode == EditPosition) EditPositionDone(TRUE);
11116     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11117     if (appData.oldSaveStyle)
11118       return SaveGameOldStyle(f);
11119     else
11120       return SaveGamePGN(f);
11121 }
11122
11123 /* Save the current position to the given file */
11124 int
11125 SavePositionToFile(filename)
11126      char *filename;
11127 {
11128     FILE *f;
11129     char buf[MSG_SIZ];
11130
11131     if (strcmp(filename, "-") == 0) {
11132         return SavePosition(stdout, 0, NULL);
11133     } else {
11134         f = fopen(filename, "a");
11135         if (f == NULL) {
11136             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11137             DisplayError(buf, errno);
11138             return FALSE;
11139         } else {
11140             SavePosition(f, 0, NULL);
11141             return TRUE;
11142         }
11143     }
11144 }
11145
11146 /* Save the current position to the given open file and close the file */
11147 int
11148 SavePosition(f, dummy, dummy2)
11149      FILE *f;
11150      int dummy;
11151      char *dummy2;
11152 {
11153     time_t tm;
11154     char *fen;
11155
11156     if (gameMode == EditPosition) EditPositionDone(TRUE);
11157     if (appData.oldSaveStyle) {
11158         tm = time((time_t *) NULL);
11159
11160         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11161         PrintOpponents(f);
11162         fprintf(f, "[--------------\n");
11163         PrintPosition(f, currentMove);
11164         fprintf(f, "--------------]\n");
11165     } else {
11166         fen = PositionToFEN(currentMove, NULL);
11167         fprintf(f, "%s\n", fen);
11168         free(fen);
11169     }
11170     fclose(f);
11171     return TRUE;
11172 }
11173
11174 void
11175 ReloadCmailMsgEvent(unregister)
11176      int unregister;
11177 {
11178 #if !WIN32
11179     static char *inFilename = NULL;
11180     static char *outFilename;
11181     int i;
11182     struct stat inbuf, outbuf;
11183     int status;
11184
11185     /* Any registered moves are unregistered if unregister is set, */
11186     /* i.e. invoked by the signal handler */
11187     if (unregister) {
11188         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11189             cmailMoveRegistered[i] = FALSE;
11190             if (cmailCommentList[i] != NULL) {
11191                 free(cmailCommentList[i]);
11192                 cmailCommentList[i] = NULL;
11193             }
11194         }
11195         nCmailMovesRegistered = 0;
11196     }
11197
11198     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11199         cmailResult[i] = CMAIL_NOT_RESULT;
11200     }
11201     nCmailResults = 0;
11202
11203     if (inFilename == NULL) {
11204         /* Because the filenames are static they only get malloced once  */
11205         /* and they never get freed                                      */
11206         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11207         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11208
11209         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11210         sprintf(outFilename, "%s.out", appData.cmailGameName);
11211     }
11212
11213     status = stat(outFilename, &outbuf);
11214     if (status < 0) {
11215         cmailMailedMove = FALSE;
11216     } else {
11217         status = stat(inFilename, &inbuf);
11218         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11219     }
11220
11221     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11222        counts the games, notes how each one terminated, etc.
11223
11224        It would be nice to remove this kludge and instead gather all
11225        the information while building the game list.  (And to keep it
11226        in the game list nodes instead of having a bunch of fixed-size
11227        parallel arrays.)  Note this will require getting each game's
11228        termination from the PGN tags, as the game list builder does
11229        not process the game moves.  --mann
11230        */
11231     cmailMsgLoaded = TRUE;
11232     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11233
11234     /* Load first game in the file or popup game menu */
11235     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11236
11237 #endif /* !WIN32 */
11238     return;
11239 }
11240
11241 int
11242 RegisterMove()
11243 {
11244     FILE *f;
11245     char string[MSG_SIZ];
11246
11247     if (   cmailMailedMove
11248         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11249         return TRUE;            /* Allow free viewing  */
11250     }
11251
11252     /* Unregister move to ensure that we don't leave RegisterMove        */
11253     /* with the move registered when the conditions for registering no   */
11254     /* longer hold                                                       */
11255     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11256         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11257         nCmailMovesRegistered --;
11258
11259         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11260           {
11261               free(cmailCommentList[lastLoadGameNumber - 1]);
11262               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11263           }
11264     }
11265
11266     if (cmailOldMove == -1) {
11267         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11268         return FALSE;
11269     }
11270
11271     if (currentMove > cmailOldMove + 1) {
11272         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11273         return FALSE;
11274     }
11275
11276     if (currentMove < cmailOldMove) {
11277         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11278         return FALSE;
11279     }
11280
11281     if (forwardMostMove > currentMove) {
11282         /* Silently truncate extra moves */
11283         TruncateGame();
11284     }
11285
11286     if (   (currentMove == cmailOldMove + 1)
11287         || (   (currentMove == cmailOldMove)
11288             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11289                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11290         if (gameInfo.result != GameUnfinished) {
11291             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11292         }
11293
11294         if (commentList[currentMove] != NULL) {
11295             cmailCommentList[lastLoadGameNumber - 1]
11296               = StrSave(commentList[currentMove]);
11297         }
11298         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11299
11300         if (appData.debugMode)
11301           fprintf(debugFP, "Saving %s for game %d\n",
11302                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11303
11304         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11305
11306         f = fopen(string, "w");
11307         if (appData.oldSaveStyle) {
11308             SaveGameOldStyle(f); /* also closes the file */
11309
11310             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11311             f = fopen(string, "w");
11312             SavePosition(f, 0, NULL); /* also closes the file */
11313         } else {
11314             fprintf(f, "{--------------\n");
11315             PrintPosition(f, currentMove);
11316             fprintf(f, "--------------}\n\n");
11317
11318             SaveGame(f, 0, NULL); /* also closes the file*/
11319         }
11320
11321         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11322         nCmailMovesRegistered ++;
11323     } else if (nCmailGames == 1) {
11324         DisplayError(_("You have not made a move yet"), 0);
11325         return FALSE;
11326     }
11327
11328     return TRUE;
11329 }
11330
11331 void
11332 MailMoveEvent()
11333 {
11334 #if !WIN32
11335     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11336     FILE *commandOutput;
11337     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11338     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11339     int nBuffers;
11340     int i;
11341     int archived;
11342     char *arcDir;
11343
11344     if (! cmailMsgLoaded) {
11345         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11346         return;
11347     }
11348
11349     if (nCmailGames == nCmailResults) {
11350         DisplayError(_("No unfinished games"), 0);
11351         return;
11352     }
11353
11354 #if CMAIL_PROHIBIT_REMAIL
11355     if (cmailMailedMove) {
11356       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);
11357         DisplayError(msg, 0);
11358         return;
11359     }
11360 #endif
11361
11362     if (! (cmailMailedMove || RegisterMove())) return;
11363
11364     if (   cmailMailedMove
11365         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11366       snprintf(string, MSG_SIZ, partCommandString,
11367                appData.debugMode ? " -v" : "", appData.cmailGameName);
11368         commandOutput = popen(string, "r");
11369
11370         if (commandOutput == NULL) {
11371             DisplayError(_("Failed to invoke cmail"), 0);
11372         } else {
11373             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11374                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11375             }
11376             if (nBuffers > 1) {
11377                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11378                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11379                 nBytes = MSG_SIZ - 1;
11380             } else {
11381                 (void) memcpy(msg, buffer, nBytes);
11382             }
11383             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11384
11385             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11386                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11387
11388                 archived = TRUE;
11389                 for (i = 0; i < nCmailGames; i ++) {
11390                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11391                         archived = FALSE;
11392                     }
11393                 }
11394                 if (   archived
11395                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11396                         != NULL)) {
11397                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11398                            arcDir,
11399                            appData.cmailGameName,
11400                            gameInfo.date);
11401                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11402                     cmailMsgLoaded = FALSE;
11403                 }
11404             }
11405
11406             DisplayInformation(msg);
11407             pclose(commandOutput);
11408         }
11409     } else {
11410         if ((*cmailMsg) != '\0') {
11411             DisplayInformation(cmailMsg);
11412         }
11413     }
11414
11415     return;
11416 #endif /* !WIN32 */
11417 }
11418
11419 char *
11420 CmailMsg()
11421 {
11422 #if WIN32
11423     return NULL;
11424 #else
11425     int  prependComma = 0;
11426     char number[5];
11427     char string[MSG_SIZ];       /* Space for game-list */
11428     int  i;
11429
11430     if (!cmailMsgLoaded) return "";
11431
11432     if (cmailMailedMove) {
11433       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11434     } else {
11435         /* Create a list of games left */
11436       snprintf(string, MSG_SIZ, "[");
11437         for (i = 0; i < nCmailGames; i ++) {
11438             if (! (   cmailMoveRegistered[i]
11439                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11440                 if (prependComma) {
11441                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11442                 } else {
11443                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11444                     prependComma = 1;
11445                 }
11446
11447                 strcat(string, number);
11448             }
11449         }
11450         strcat(string, "]");
11451
11452         if (nCmailMovesRegistered + nCmailResults == 0) {
11453             switch (nCmailGames) {
11454               case 1:
11455                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11456                 break;
11457
11458               case 2:
11459                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11460                 break;
11461
11462               default:
11463                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11464                          nCmailGames);
11465                 break;
11466             }
11467         } else {
11468             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11469               case 1:
11470                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11471                          string);
11472                 break;
11473
11474               case 0:
11475                 if (nCmailResults == nCmailGames) {
11476                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11477                 } else {
11478                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11479                 }
11480                 break;
11481
11482               default:
11483                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11484                          string);
11485             }
11486         }
11487     }
11488     return cmailMsg;
11489 #endif /* WIN32 */
11490 }
11491
11492 void
11493 ResetGameEvent()
11494 {
11495     if (gameMode == Training)
11496       SetTrainingModeOff();
11497
11498     Reset(TRUE, TRUE);
11499     cmailMsgLoaded = FALSE;
11500     if (appData.icsActive) {
11501       SendToICS(ics_prefix);
11502       SendToICS("refresh\n");
11503     }
11504 }
11505
11506 void
11507 ExitEvent(status)
11508      int status;
11509 {
11510     exiting++;
11511     if (exiting > 2) {
11512       /* Give up on clean exit */
11513       exit(status);
11514     }
11515     if (exiting > 1) {
11516       /* Keep trying for clean exit */
11517       return;
11518     }
11519
11520     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11521
11522     if (telnetISR != NULL) {
11523       RemoveInputSource(telnetISR);
11524     }
11525     if (icsPR != NoProc) {
11526       DestroyChildProcess(icsPR, TRUE);
11527     }
11528
11529     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11530     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11531
11532     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11533     /* make sure this other one finishes before killing it!                  */
11534     if(endingGame) { int count = 0;
11535         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11536         while(endingGame && count++ < 10) DoSleep(1);
11537         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11538     }
11539
11540     /* Kill off chess programs */
11541     if (first.pr != NoProc) {
11542         ExitAnalyzeMode();
11543
11544         DoSleep( appData.delayBeforeQuit );
11545         SendToProgram("quit\n", &first);
11546         DoSleep( appData.delayAfterQuit );
11547         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11548     }
11549     if (second.pr != NoProc) {
11550         DoSleep( appData.delayBeforeQuit );
11551         SendToProgram("quit\n", &second);
11552         DoSleep( appData.delayAfterQuit );
11553         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11554     }
11555     if (first.isr != NULL) {
11556         RemoveInputSource(first.isr);
11557     }
11558     if (second.isr != NULL) {
11559         RemoveInputSource(second.isr);
11560     }
11561
11562     ShutDownFrontEnd();
11563     exit(status);
11564 }
11565
11566 void
11567 PauseEvent()
11568 {
11569     if (appData.debugMode)
11570         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11571     if (pausing) {
11572         pausing = FALSE;
11573         ModeHighlight();
11574         if (gameMode == MachinePlaysWhite ||
11575             gameMode == MachinePlaysBlack) {
11576             StartClocks();
11577         } else {
11578             DisplayBothClocks();
11579         }
11580         if (gameMode == PlayFromGameFile) {
11581             if (appData.timeDelay >= 0)
11582                 AutoPlayGameLoop();
11583         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11584             Reset(FALSE, TRUE);
11585             SendToICS(ics_prefix);
11586             SendToICS("refresh\n");
11587         } else if (currentMove < forwardMostMove) {
11588             ForwardInner(forwardMostMove);
11589         }
11590         pauseExamInvalid = FALSE;
11591     } else {
11592         switch (gameMode) {
11593           default:
11594             return;
11595           case IcsExamining:
11596             pauseExamForwardMostMove = forwardMostMove;
11597             pauseExamInvalid = FALSE;
11598             /* fall through */
11599           case IcsObserving:
11600           case IcsPlayingWhite:
11601           case IcsPlayingBlack:
11602             pausing = TRUE;
11603             ModeHighlight();
11604             return;
11605           case PlayFromGameFile:
11606             (void) StopLoadGameTimer();
11607             pausing = TRUE;
11608             ModeHighlight();
11609             break;
11610           case BeginningOfGame:
11611             if (appData.icsActive) return;
11612             /* else fall through */
11613           case MachinePlaysWhite:
11614           case MachinePlaysBlack:
11615           case TwoMachinesPlay:
11616             if (forwardMostMove == 0)
11617               return;           /* don't pause if no one has moved */
11618             if ((gameMode == MachinePlaysWhite &&
11619                  !WhiteOnMove(forwardMostMove)) ||
11620                 (gameMode == MachinePlaysBlack &&
11621                  WhiteOnMove(forwardMostMove))) {
11622                 StopClocks();
11623             }
11624             pausing = TRUE;
11625             ModeHighlight();
11626             break;
11627         }
11628     }
11629 }
11630
11631 void
11632 EditCommentEvent()
11633 {
11634     char title[MSG_SIZ];
11635
11636     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11637       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11638     } else {
11639       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11640                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11641                parseList[currentMove - 1]);
11642     }
11643
11644     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11645 }
11646
11647
11648 void
11649 EditTagsEvent()
11650 {
11651     char *tags = PGNTags(&gameInfo);
11652     EditTagsPopUp(tags);
11653     free(tags);
11654 }
11655
11656 void
11657 AnalyzeModeEvent()
11658 {
11659     if (appData.noChessProgram || gameMode == AnalyzeMode)
11660       return;
11661
11662     if (gameMode != AnalyzeFile) {
11663         if (!appData.icsEngineAnalyze) {
11664                EditGameEvent();
11665                if (gameMode != EditGame) return;
11666         }
11667         ResurrectChessProgram();
11668         SendToProgram("analyze\n", &first);
11669         first.analyzing = TRUE;
11670         /*first.maybeThinking = TRUE;*/
11671         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11672         EngineOutputPopUp();
11673     }
11674     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11675     pausing = FALSE;
11676     ModeHighlight();
11677     SetGameInfo();
11678
11679     StartAnalysisClock();
11680     GetTimeMark(&lastNodeCountTime);
11681     lastNodeCount = 0;
11682 }
11683
11684 void
11685 AnalyzeFileEvent()
11686 {
11687     if (appData.noChessProgram || gameMode == AnalyzeFile)
11688       return;
11689
11690     if (gameMode != AnalyzeMode) {
11691         EditGameEvent();
11692         if (gameMode != EditGame) return;
11693         ResurrectChessProgram();
11694         SendToProgram("analyze\n", &first);
11695         first.analyzing = TRUE;
11696         /*first.maybeThinking = TRUE;*/
11697         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11698         EngineOutputPopUp();
11699     }
11700     gameMode = AnalyzeFile;
11701     pausing = FALSE;
11702     ModeHighlight();
11703     SetGameInfo();
11704
11705     StartAnalysisClock();
11706     GetTimeMark(&lastNodeCountTime);
11707     lastNodeCount = 0;
11708 }
11709
11710 void
11711 MachineWhiteEvent()
11712 {
11713     char buf[MSG_SIZ];
11714     char *bookHit = NULL;
11715
11716     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11717       return;
11718
11719
11720     if (gameMode == PlayFromGameFile ||
11721         gameMode == TwoMachinesPlay  ||
11722         gameMode == Training         ||
11723         gameMode == AnalyzeMode      ||
11724         gameMode == EndOfGame)
11725         EditGameEvent();
11726
11727     if (gameMode == EditPosition)
11728         EditPositionDone(TRUE);
11729
11730     if (!WhiteOnMove(currentMove)) {
11731         DisplayError(_("It is not White's turn"), 0);
11732         return;
11733     }
11734
11735     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11736       ExitAnalyzeMode();
11737
11738     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11739         gameMode == AnalyzeFile)
11740         TruncateGame();
11741
11742     ResurrectChessProgram();    /* in case it isn't running */
11743     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11744         gameMode = MachinePlaysWhite;
11745         ResetClocks();
11746     } else
11747     gameMode = MachinePlaysWhite;
11748     pausing = FALSE;
11749     ModeHighlight();
11750     SetGameInfo();
11751     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11752     DisplayTitle(buf);
11753     if (first.sendName) {
11754       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11755       SendToProgram(buf, &first);
11756     }
11757     if (first.sendTime) {
11758       if (first.useColors) {
11759         SendToProgram("black\n", &first); /*gnu kludge*/
11760       }
11761       SendTimeRemaining(&first, TRUE);
11762     }
11763     if (first.useColors) {
11764       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11765     }
11766     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11767     SetMachineThinkingEnables();
11768     first.maybeThinking = TRUE;
11769     StartClocks();
11770     firstMove = FALSE;
11771
11772     if (appData.autoFlipView && !flipView) {
11773       flipView = !flipView;
11774       DrawPosition(FALSE, NULL);
11775       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11776     }
11777
11778     if(bookHit) { // [HGM] book: simulate book reply
11779         static char bookMove[MSG_SIZ]; // a bit generous?
11780
11781         programStats.nodes = programStats.depth = programStats.time =
11782         programStats.score = programStats.got_only_move = 0;
11783         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11784
11785         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11786         strcat(bookMove, bookHit);
11787         HandleMachineMove(bookMove, &first);
11788     }
11789 }
11790
11791 void
11792 MachineBlackEvent()
11793 {
11794   char buf[MSG_SIZ];
11795   char *bookHit = NULL;
11796
11797     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11798         return;
11799
11800
11801     if (gameMode == PlayFromGameFile ||
11802         gameMode == TwoMachinesPlay  ||
11803         gameMode == Training         ||
11804         gameMode == AnalyzeMode      ||
11805         gameMode == EndOfGame)
11806         EditGameEvent();
11807
11808     if (gameMode == EditPosition)
11809         EditPositionDone(TRUE);
11810
11811     if (WhiteOnMove(currentMove)) {
11812         DisplayError(_("It is not Black's turn"), 0);
11813         return;
11814     }
11815
11816     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11817       ExitAnalyzeMode();
11818
11819     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11820         gameMode == AnalyzeFile)
11821         TruncateGame();
11822
11823     ResurrectChessProgram();    /* in case it isn't running */
11824     gameMode = MachinePlaysBlack;
11825     pausing = FALSE;
11826     ModeHighlight();
11827     SetGameInfo();
11828     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11829     DisplayTitle(buf);
11830     if (first.sendName) {
11831       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11832       SendToProgram(buf, &first);
11833     }
11834     if (first.sendTime) {
11835       if (first.useColors) {
11836         SendToProgram("white\n", &first); /*gnu kludge*/
11837       }
11838       SendTimeRemaining(&first, FALSE);
11839     }
11840     if (first.useColors) {
11841       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11842     }
11843     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11844     SetMachineThinkingEnables();
11845     first.maybeThinking = TRUE;
11846     StartClocks();
11847
11848     if (appData.autoFlipView && flipView) {
11849       flipView = !flipView;
11850       DrawPosition(FALSE, NULL);
11851       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11852     }
11853     if(bookHit) { // [HGM] book: simulate book reply
11854         static char bookMove[MSG_SIZ]; // a bit generous?
11855
11856         programStats.nodes = programStats.depth = programStats.time =
11857         programStats.score = programStats.got_only_move = 0;
11858         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11859
11860         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11861         strcat(bookMove, bookHit);
11862         HandleMachineMove(bookMove, &first);
11863     }
11864 }
11865
11866
11867 void
11868 DisplayTwoMachinesTitle()
11869 {
11870     char buf[MSG_SIZ];
11871     if (appData.matchGames > 0) {
11872         if (first.twoMachinesColor[0] == 'w') {
11873           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11874                    gameInfo.white, gameInfo.black,
11875                    first.matchWins, second.matchWins,
11876                    matchGame - 1 - (first.matchWins + second.matchWins));
11877         } else {
11878           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11879                    gameInfo.white, gameInfo.black,
11880                    second.matchWins, first.matchWins,
11881                    matchGame - 1 - (first.matchWins + second.matchWins));
11882         }
11883     } else {
11884       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11885     }
11886     DisplayTitle(buf);
11887 }
11888
11889 void
11890 SettingsMenuIfReady()
11891 {
11892   if (second.lastPing != second.lastPong) {
11893     DisplayMessage("", _("Waiting for second chess program"));
11894     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11895     return;
11896   }
11897   ThawUI();
11898   DisplayMessage("", "");
11899   SettingsPopUp(&second);
11900 }
11901
11902 int
11903 WaitForSecond(DelayedEventCallback retry)
11904 {
11905     if (second.pr == NULL) {
11906         StartChessProgram(&second);
11907         if (second.protocolVersion == 1) {
11908           retry();
11909         } else {
11910           /* kludge: allow timeout for initial "feature" command */
11911           FreezeUI();
11912           DisplayMessage("", _("Starting second chess program"));
11913           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11914         }
11915         return 1;
11916     }
11917     return 0;
11918 }
11919
11920 void
11921 TwoMachinesEvent P((void))
11922 {
11923     int i;
11924     char buf[MSG_SIZ];
11925     ChessProgramState *onmove;
11926     char *bookHit = NULL;
11927
11928     if (appData.noChessProgram) return;
11929
11930     switch (gameMode) {
11931       case TwoMachinesPlay:
11932         return;
11933       case MachinePlaysWhite:
11934       case MachinePlaysBlack:
11935         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11936             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11937             return;
11938         }
11939         /* fall through */
11940       case BeginningOfGame:
11941       case PlayFromGameFile:
11942       case EndOfGame:
11943         EditGameEvent();
11944         if (gameMode != EditGame) return;
11945         break;
11946       case EditPosition:
11947         EditPositionDone(TRUE);
11948         break;
11949       case AnalyzeMode:
11950       case AnalyzeFile:
11951         ExitAnalyzeMode();
11952         break;
11953       case EditGame:
11954       default:
11955         break;
11956     }
11957
11958 //    forwardMostMove = currentMove;
11959     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11960     ResurrectChessProgram();    /* in case first program isn't running */
11961
11962     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11963     DisplayMessage("", "");
11964     InitChessProgram(&second, FALSE);
11965     SendToProgram("force\n", &second);
11966     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11967       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11968       return;
11969     }
11970     if (startedFromSetupPosition) {
11971         SendBoard(&second, backwardMostMove);
11972     if (appData.debugMode) {
11973         fprintf(debugFP, "Two Machines\n");
11974     }
11975     }
11976     for (i = backwardMostMove; i < forwardMostMove; i++) {
11977         SendMoveToProgram(i, &second);
11978     }
11979
11980     gameMode = TwoMachinesPlay;
11981     pausing = FALSE;
11982     ModeHighlight();
11983     SetGameInfo();
11984     DisplayTwoMachinesTitle();
11985     firstMove = TRUE;
11986     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11987         onmove = &first;
11988     } else {
11989         onmove = &second;
11990     }
11991
11992     SendToProgram(first.computerString, &first);
11993     if (first.sendName) {
11994       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11995       SendToProgram(buf, &first);
11996     }
11997     SendToProgram(second.computerString, &second);
11998     if (second.sendName) {
11999       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12000       SendToProgram(buf, &second);
12001     }
12002
12003     ResetClocks();
12004     if (!first.sendTime || !second.sendTime) {
12005         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12006         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12007     }
12008     if (onmove->sendTime) {
12009       if (onmove->useColors) {
12010         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12011       }
12012       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12013     }
12014     if (onmove->useColors) {
12015       SendToProgram(onmove->twoMachinesColor, onmove);
12016     }
12017     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12018 //    SendToProgram("go\n", onmove);
12019     onmove->maybeThinking = TRUE;
12020     SetMachineThinkingEnables();
12021
12022     StartClocks();
12023
12024     if(bookHit) { // [HGM] book: simulate book reply
12025         static char bookMove[MSG_SIZ]; // a bit generous?
12026
12027         programStats.nodes = programStats.depth = programStats.time =
12028         programStats.score = programStats.got_only_move = 0;
12029         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12030
12031         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12032         strcat(bookMove, bookHit);
12033         savedMessage = bookMove; // args for deferred call
12034         savedState = onmove;
12035         ScheduleDelayedEvent(DeferredBookMove, 1);
12036     }
12037 }
12038
12039 void
12040 TrainingEvent()
12041 {
12042     if (gameMode == Training) {
12043       SetTrainingModeOff();
12044       gameMode = PlayFromGameFile;
12045       DisplayMessage("", _("Training mode off"));
12046     } else {
12047       gameMode = Training;
12048       animateTraining = appData.animate;
12049
12050       /* make sure we are not already at the end of the game */
12051       if (currentMove < forwardMostMove) {
12052         SetTrainingModeOn();
12053         DisplayMessage("", _("Training mode on"));
12054       } else {
12055         gameMode = PlayFromGameFile;
12056         DisplayError(_("Already at end of game"), 0);
12057       }
12058     }
12059     ModeHighlight();
12060 }
12061
12062 void
12063 IcsClientEvent()
12064 {
12065     if (!appData.icsActive) return;
12066     switch (gameMode) {
12067       case IcsPlayingWhite:
12068       case IcsPlayingBlack:
12069       case IcsObserving:
12070       case IcsIdle:
12071       case BeginningOfGame:
12072       case IcsExamining:
12073         return;
12074
12075       case EditGame:
12076         break;
12077
12078       case EditPosition:
12079         EditPositionDone(TRUE);
12080         break;
12081
12082       case AnalyzeMode:
12083       case AnalyzeFile:
12084         ExitAnalyzeMode();
12085         break;
12086
12087       default:
12088         EditGameEvent();
12089         break;
12090     }
12091
12092     gameMode = IcsIdle;
12093     ModeHighlight();
12094     return;
12095 }
12096
12097
12098 void
12099 EditGameEvent()
12100 {
12101     int i;
12102
12103     switch (gameMode) {
12104       case Training:
12105         SetTrainingModeOff();
12106         break;
12107       case MachinePlaysWhite:
12108       case MachinePlaysBlack:
12109       case BeginningOfGame:
12110         SendToProgram("force\n", &first);
12111         SetUserThinkingEnables();
12112         break;
12113       case PlayFromGameFile:
12114         (void) StopLoadGameTimer();
12115         if (gameFileFP != NULL) {
12116             gameFileFP = NULL;
12117         }
12118         break;
12119       case EditPosition:
12120         EditPositionDone(TRUE);
12121         break;
12122       case AnalyzeMode:
12123       case AnalyzeFile:
12124         ExitAnalyzeMode();
12125         SendToProgram("force\n", &first);
12126         break;
12127       case TwoMachinesPlay:
12128         GameEnds(EndOfFile, NULL, GE_PLAYER);
12129         ResurrectChessProgram();
12130         SetUserThinkingEnables();
12131         break;
12132       case EndOfGame:
12133         ResurrectChessProgram();
12134         break;
12135       case IcsPlayingBlack:
12136       case IcsPlayingWhite:
12137         DisplayError(_("Warning: You are still playing a game"), 0);
12138         break;
12139       case IcsObserving:
12140         DisplayError(_("Warning: You are still observing a game"), 0);
12141         break;
12142       case IcsExamining:
12143         DisplayError(_("Warning: You are still examining a game"), 0);
12144         break;
12145       case IcsIdle:
12146         break;
12147       case EditGame:
12148       default:
12149         return;
12150     }
12151
12152     pausing = FALSE;
12153     StopClocks();
12154     first.offeredDraw = second.offeredDraw = 0;
12155
12156     if (gameMode == PlayFromGameFile) {
12157         whiteTimeRemaining = timeRemaining[0][currentMove];
12158         blackTimeRemaining = timeRemaining[1][currentMove];
12159         DisplayTitle("");
12160     }
12161
12162     if (gameMode == MachinePlaysWhite ||
12163         gameMode == MachinePlaysBlack ||
12164         gameMode == TwoMachinesPlay ||
12165         gameMode == EndOfGame) {
12166         i = forwardMostMove;
12167         while (i > currentMove) {
12168             SendToProgram("undo\n", &first);
12169             i--;
12170         }
12171         whiteTimeRemaining = timeRemaining[0][currentMove];
12172         blackTimeRemaining = timeRemaining[1][currentMove];
12173         DisplayBothClocks();
12174         if (whiteFlag || blackFlag) {
12175             whiteFlag = blackFlag = 0;
12176         }
12177         DisplayTitle("");
12178     }
12179
12180     gameMode = EditGame;
12181     ModeHighlight();
12182     SetGameInfo();
12183 }
12184
12185
12186 void
12187 EditPositionEvent()
12188 {
12189     if (gameMode == EditPosition) {
12190         EditGameEvent();
12191         return;
12192     }
12193
12194     EditGameEvent();
12195     if (gameMode != EditGame) return;
12196
12197     gameMode = EditPosition;
12198     ModeHighlight();
12199     SetGameInfo();
12200     if (currentMove > 0)
12201       CopyBoard(boards[0], boards[currentMove]);
12202
12203     blackPlaysFirst = !WhiteOnMove(currentMove);
12204     ResetClocks();
12205     currentMove = forwardMostMove = backwardMostMove = 0;
12206     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12207     DisplayMove(-1);
12208 }
12209
12210 void
12211 ExitAnalyzeMode()
12212 {
12213     /* [DM] icsEngineAnalyze - possible call from other functions */
12214     if (appData.icsEngineAnalyze) {
12215         appData.icsEngineAnalyze = FALSE;
12216
12217         DisplayMessage("",_("Close ICS engine analyze..."));
12218     }
12219     if (first.analysisSupport && first.analyzing) {
12220       SendToProgram("exit\n", &first);
12221       first.analyzing = FALSE;
12222     }
12223     thinkOutput[0] = NULLCHAR;
12224 }
12225
12226 void
12227 EditPositionDone(Boolean fakeRights)
12228 {
12229     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12230
12231     startedFromSetupPosition = TRUE;
12232     InitChessProgram(&first, FALSE);
12233     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12234       boards[0][EP_STATUS] = EP_NONE;
12235       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12236     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12237         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12238         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12239       } else boards[0][CASTLING][2] = NoRights;
12240     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12241         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12242         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12243       } else boards[0][CASTLING][5] = NoRights;
12244     }
12245     SendToProgram("force\n", &first);
12246     if (blackPlaysFirst) {
12247         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12248         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12249         currentMove = forwardMostMove = backwardMostMove = 1;
12250         CopyBoard(boards[1], boards[0]);
12251     } else {
12252         currentMove = forwardMostMove = backwardMostMove = 0;
12253     }
12254     SendBoard(&first, forwardMostMove);
12255     if (appData.debugMode) {
12256         fprintf(debugFP, "EditPosDone\n");
12257     }
12258     DisplayTitle("");
12259     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12260     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12261     gameMode = EditGame;
12262     ModeHighlight();
12263     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12264     ClearHighlights(); /* [AS] */
12265 }
12266
12267 /* Pause for `ms' milliseconds */
12268 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12269 void
12270 TimeDelay(ms)
12271      long ms;
12272 {
12273     TimeMark m1, m2;
12274
12275     GetTimeMark(&m1);
12276     do {
12277         GetTimeMark(&m2);
12278     } while (SubtractTimeMarks(&m2, &m1) < ms);
12279 }
12280
12281 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12282 void
12283 SendMultiLineToICS(buf)
12284      char *buf;
12285 {
12286     char temp[MSG_SIZ+1], *p;
12287     int len;
12288
12289     len = strlen(buf);
12290     if (len > MSG_SIZ)
12291       len = MSG_SIZ;
12292
12293     strncpy(temp, buf, len);
12294     temp[len] = 0;
12295
12296     p = temp;
12297     while (*p) {
12298         if (*p == '\n' || *p == '\r')
12299           *p = ' ';
12300         ++p;
12301     }
12302
12303     strcat(temp, "\n");
12304     SendToICS(temp);
12305     SendToPlayer(temp, strlen(temp));
12306 }
12307
12308 void
12309 SetWhiteToPlayEvent()
12310 {
12311     if (gameMode == EditPosition) {
12312         blackPlaysFirst = FALSE;
12313         DisplayBothClocks();    /* works because currentMove is 0 */
12314     } else if (gameMode == IcsExamining) {
12315         SendToICS(ics_prefix);
12316         SendToICS("tomove white\n");
12317     }
12318 }
12319
12320 void
12321 SetBlackToPlayEvent()
12322 {
12323     if (gameMode == EditPosition) {
12324         blackPlaysFirst = TRUE;
12325         currentMove = 1;        /* kludge */
12326         DisplayBothClocks();
12327         currentMove = 0;
12328     } else if (gameMode == IcsExamining) {
12329         SendToICS(ics_prefix);
12330         SendToICS("tomove black\n");
12331     }
12332 }
12333
12334 void
12335 EditPositionMenuEvent(selection, x, y)
12336      ChessSquare selection;
12337      int x, y;
12338 {
12339     char buf[MSG_SIZ];
12340     ChessSquare piece = boards[0][y][x];
12341
12342     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12343
12344     switch (selection) {
12345       case ClearBoard:
12346         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12347             SendToICS(ics_prefix);
12348             SendToICS("bsetup clear\n");
12349         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12350             SendToICS(ics_prefix);
12351             SendToICS("clearboard\n");
12352         } else {
12353             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12354                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12355                 for (y = 0; y < BOARD_HEIGHT; y++) {
12356                     if (gameMode == IcsExamining) {
12357                         if (boards[currentMove][y][x] != EmptySquare) {
12358                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12359                                     AAA + x, ONE + y);
12360                             SendToICS(buf);
12361                         }
12362                     } else {
12363                         boards[0][y][x] = p;
12364                     }
12365                 }
12366             }
12367         }
12368         if (gameMode == EditPosition) {
12369             DrawPosition(FALSE, boards[0]);
12370         }
12371         break;
12372
12373       case WhitePlay:
12374         SetWhiteToPlayEvent();
12375         break;
12376
12377       case BlackPlay:
12378         SetBlackToPlayEvent();
12379         break;
12380
12381       case EmptySquare:
12382         if (gameMode == IcsExamining) {
12383             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12384             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12385             SendToICS(buf);
12386         } else {
12387             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12388                 if(x == BOARD_LEFT-2) {
12389                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12390                     boards[0][y][1] = 0;
12391                 } else
12392                 if(x == BOARD_RGHT+1) {
12393                     if(y >= gameInfo.holdingsSize) break;
12394                     boards[0][y][BOARD_WIDTH-2] = 0;
12395                 } else break;
12396             }
12397             boards[0][y][x] = EmptySquare;
12398             DrawPosition(FALSE, boards[0]);
12399         }
12400         break;
12401
12402       case PromotePiece:
12403         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12404            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12405             selection = (ChessSquare) (PROMOTED piece);
12406         } else if(piece == EmptySquare) selection = WhiteSilver;
12407         else selection = (ChessSquare)((int)piece - 1);
12408         goto defaultlabel;
12409
12410       case DemotePiece:
12411         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12412            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12413             selection = (ChessSquare) (DEMOTED piece);
12414         } else if(piece == EmptySquare) selection = BlackSilver;
12415         else selection = (ChessSquare)((int)piece + 1);
12416         goto defaultlabel;
12417
12418       case WhiteQueen:
12419       case BlackQueen:
12420         if(gameInfo.variant == VariantShatranj ||
12421            gameInfo.variant == VariantXiangqi  ||
12422            gameInfo.variant == VariantCourier  ||
12423            gameInfo.variant == VariantMakruk     )
12424             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12425         goto defaultlabel;
12426
12427       case WhiteKing:
12428       case BlackKing:
12429         if(gameInfo.variant == VariantXiangqi)
12430             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12431         if(gameInfo.variant == VariantKnightmate)
12432             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12433       default:
12434         defaultlabel:
12435         if (gameMode == IcsExamining) {
12436             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12437             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12438                      PieceToChar(selection), AAA + x, ONE + y);
12439             SendToICS(buf);
12440         } else {
12441             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12442                 int n;
12443                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12444                     n = PieceToNumber(selection - BlackPawn);
12445                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12446                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12447                     boards[0][BOARD_HEIGHT-1-n][1]++;
12448                 } else
12449                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12450                     n = PieceToNumber(selection);
12451                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12452                     boards[0][n][BOARD_WIDTH-1] = selection;
12453                     boards[0][n][BOARD_WIDTH-2]++;
12454                 }
12455             } else
12456             boards[0][y][x] = selection;
12457             DrawPosition(TRUE, boards[0]);
12458         }
12459         break;
12460     }
12461 }
12462
12463
12464 void
12465 DropMenuEvent(selection, x, y)
12466      ChessSquare selection;
12467      int x, y;
12468 {
12469     ChessMove moveType;
12470
12471     switch (gameMode) {
12472       case IcsPlayingWhite:
12473       case MachinePlaysBlack:
12474         if (!WhiteOnMove(currentMove)) {
12475             DisplayMoveError(_("It is Black's turn"));
12476             return;
12477         }
12478         moveType = WhiteDrop;
12479         break;
12480       case IcsPlayingBlack:
12481       case MachinePlaysWhite:
12482         if (WhiteOnMove(currentMove)) {
12483             DisplayMoveError(_("It is White's turn"));
12484             return;
12485         }
12486         moveType = BlackDrop;
12487         break;
12488       case EditGame:
12489         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12490         break;
12491       default:
12492         return;
12493     }
12494
12495     if (moveType == BlackDrop && selection < BlackPawn) {
12496       selection = (ChessSquare) ((int) selection
12497                                  + (int) BlackPawn - (int) WhitePawn);
12498     }
12499     if (boards[currentMove][y][x] != EmptySquare) {
12500         DisplayMoveError(_("That square is occupied"));
12501         return;
12502     }
12503
12504     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12505 }
12506
12507 void
12508 AcceptEvent()
12509 {
12510     /* Accept a pending offer of any kind from opponent */
12511
12512     if (appData.icsActive) {
12513         SendToICS(ics_prefix);
12514         SendToICS("accept\n");
12515     } else if (cmailMsgLoaded) {
12516         if (currentMove == cmailOldMove &&
12517             commentList[cmailOldMove] != NULL &&
12518             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12519                    "Black offers a draw" : "White offers a draw")) {
12520             TruncateGame();
12521             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12522             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12523         } else {
12524             DisplayError(_("There is no pending offer on this move"), 0);
12525             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12526         }
12527     } else {
12528         /* Not used for offers from chess program */
12529     }
12530 }
12531
12532 void
12533 DeclineEvent()
12534 {
12535     /* Decline a pending offer of any kind from opponent */
12536
12537     if (appData.icsActive) {
12538         SendToICS(ics_prefix);
12539         SendToICS("decline\n");
12540     } else if (cmailMsgLoaded) {
12541         if (currentMove == cmailOldMove &&
12542             commentList[cmailOldMove] != NULL &&
12543             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12544                    "Black offers a draw" : "White offers a draw")) {
12545 #ifdef NOTDEF
12546             AppendComment(cmailOldMove, "Draw declined", TRUE);
12547             DisplayComment(cmailOldMove - 1, "Draw declined");
12548 #endif /*NOTDEF*/
12549         } else {
12550             DisplayError(_("There is no pending offer on this move"), 0);
12551         }
12552     } else {
12553         /* Not used for offers from chess program */
12554     }
12555 }
12556
12557 void
12558 RematchEvent()
12559 {
12560     /* Issue ICS rematch command */
12561     if (appData.icsActive) {
12562         SendToICS(ics_prefix);
12563         SendToICS("rematch\n");
12564     }
12565 }
12566
12567 void
12568 CallFlagEvent()
12569 {
12570     /* Call your opponent's flag (claim a win on time) */
12571     if (appData.icsActive) {
12572         SendToICS(ics_prefix);
12573         SendToICS("flag\n");
12574     } else {
12575         switch (gameMode) {
12576           default:
12577             return;
12578           case MachinePlaysWhite:
12579             if (whiteFlag) {
12580                 if (blackFlag)
12581                   GameEnds(GameIsDrawn, "Both players ran out of time",
12582                            GE_PLAYER);
12583                 else
12584                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12585             } else {
12586                 DisplayError(_("Your opponent is not out of time"), 0);
12587             }
12588             break;
12589           case MachinePlaysBlack:
12590             if (blackFlag) {
12591                 if (whiteFlag)
12592                   GameEnds(GameIsDrawn, "Both players ran out of time",
12593                            GE_PLAYER);
12594                 else
12595                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12596             } else {
12597                 DisplayError(_("Your opponent is not out of time"), 0);
12598             }
12599             break;
12600         }
12601     }
12602 }
12603
12604 void
12605 DrawEvent()
12606 {
12607     /* Offer draw or accept pending draw offer from opponent */
12608
12609     if (appData.icsActive) {
12610         /* Note: tournament rules require draw offers to be
12611            made after you make your move but before you punch
12612            your clock.  Currently ICS doesn't let you do that;
12613            instead, you immediately punch your clock after making
12614            a move, but you can offer a draw at any time. */
12615
12616         SendToICS(ics_prefix);
12617         SendToICS("draw\n");
12618         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12619     } else if (cmailMsgLoaded) {
12620         if (currentMove == cmailOldMove &&
12621             commentList[cmailOldMove] != NULL &&
12622             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12623                    "Black offers a draw" : "White offers a draw")) {
12624             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12625             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12626         } else if (currentMove == cmailOldMove + 1) {
12627             char *offer = WhiteOnMove(cmailOldMove) ?
12628               "White offers a draw" : "Black offers a draw";
12629             AppendComment(currentMove, offer, TRUE);
12630             DisplayComment(currentMove - 1, offer);
12631             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12632         } else {
12633             DisplayError(_("You must make your move before offering a draw"), 0);
12634             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12635         }
12636     } else if (first.offeredDraw) {
12637         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12638     } else {
12639         if (first.sendDrawOffers) {
12640             SendToProgram("draw\n", &first);
12641             userOfferedDraw = TRUE;
12642         }
12643     }
12644 }
12645
12646 void
12647 AdjournEvent()
12648 {
12649     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12650
12651     if (appData.icsActive) {
12652         SendToICS(ics_prefix);
12653         SendToICS("adjourn\n");
12654     } else {
12655         /* Currently GNU Chess doesn't offer or accept Adjourns */
12656     }
12657 }
12658
12659
12660 void
12661 AbortEvent()
12662 {
12663     /* Offer Abort or accept pending Abort offer from opponent */
12664
12665     if (appData.icsActive) {
12666         SendToICS(ics_prefix);
12667         SendToICS("abort\n");
12668     } else {
12669         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12670     }
12671 }
12672
12673 void
12674 ResignEvent()
12675 {
12676     /* Resign.  You can do this even if it's not your turn. */
12677
12678     if (appData.icsActive) {
12679         SendToICS(ics_prefix);
12680         SendToICS("resign\n");
12681     } else {
12682         switch (gameMode) {
12683           case MachinePlaysWhite:
12684             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12685             break;
12686           case MachinePlaysBlack:
12687             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12688             break;
12689           case EditGame:
12690             if (cmailMsgLoaded) {
12691                 TruncateGame();
12692                 if (WhiteOnMove(cmailOldMove)) {
12693                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12694                 } else {
12695                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12696                 }
12697                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12698             }
12699             break;
12700           default:
12701             break;
12702         }
12703     }
12704 }
12705
12706
12707 void
12708 StopObservingEvent()
12709 {
12710     /* Stop observing current games */
12711     SendToICS(ics_prefix);
12712     SendToICS("unobserve\n");
12713 }
12714
12715 void
12716 StopExaminingEvent()
12717 {
12718     /* Stop observing current game */
12719     SendToICS(ics_prefix);
12720     SendToICS("unexamine\n");
12721 }
12722
12723 void
12724 ForwardInner(target)
12725      int target;
12726 {
12727     int limit;
12728
12729     if (appData.debugMode)
12730         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12731                 target, currentMove, forwardMostMove);
12732
12733     if (gameMode == EditPosition)
12734       return;
12735
12736     if (gameMode == PlayFromGameFile && !pausing)
12737       PauseEvent();
12738
12739     if (gameMode == IcsExamining && pausing)
12740       limit = pauseExamForwardMostMove;
12741     else
12742       limit = forwardMostMove;
12743
12744     if (target > limit) target = limit;
12745
12746     if (target > 0 && moveList[target - 1][0]) {
12747         int fromX, fromY, toX, toY;
12748         toX = moveList[target - 1][2] - AAA;
12749         toY = moveList[target - 1][3] - ONE;
12750         if (moveList[target - 1][1] == '@') {
12751             if (appData.highlightLastMove) {
12752                 SetHighlights(-1, -1, toX, toY);
12753             }
12754         } else {
12755             fromX = moveList[target - 1][0] - AAA;
12756             fromY = moveList[target - 1][1] - ONE;
12757             if (target == currentMove + 1) {
12758                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12759             }
12760             if (appData.highlightLastMove) {
12761                 SetHighlights(fromX, fromY, toX, toY);
12762             }
12763         }
12764     }
12765     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12766         gameMode == Training || gameMode == PlayFromGameFile ||
12767         gameMode == AnalyzeFile) {
12768         while (currentMove < target) {
12769             SendMoveToProgram(currentMove++, &first);
12770         }
12771     } else {
12772         currentMove = target;
12773     }
12774
12775     if (gameMode == EditGame || gameMode == EndOfGame) {
12776         whiteTimeRemaining = timeRemaining[0][currentMove];
12777         blackTimeRemaining = timeRemaining[1][currentMove];
12778     }
12779     DisplayBothClocks();
12780     DisplayMove(currentMove - 1);
12781     DrawPosition(FALSE, boards[currentMove]);
12782     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12783     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12784         DisplayComment(currentMove - 1, commentList[currentMove]);
12785     }
12786 }
12787
12788
12789 void
12790 ForwardEvent()
12791 {
12792     if (gameMode == IcsExamining && !pausing) {
12793         SendToICS(ics_prefix);
12794         SendToICS("forward\n");
12795     } else {
12796         ForwardInner(currentMove + 1);
12797     }
12798 }
12799
12800 void
12801 ToEndEvent()
12802 {
12803     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12804         /* to optimze, we temporarily turn off analysis mode while we feed
12805          * the remaining moves to the engine. Otherwise we get analysis output
12806          * after each move.
12807          */
12808         if (first.analysisSupport) {
12809           SendToProgram("exit\nforce\n", &first);
12810           first.analyzing = FALSE;
12811         }
12812     }
12813
12814     if (gameMode == IcsExamining && !pausing) {
12815         SendToICS(ics_prefix);
12816         SendToICS("forward 999999\n");
12817     } else {
12818         ForwardInner(forwardMostMove);
12819     }
12820
12821     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12822         /* we have fed all the moves, so reactivate analysis mode */
12823         SendToProgram("analyze\n", &first);
12824         first.analyzing = TRUE;
12825         /*first.maybeThinking = TRUE;*/
12826         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12827     }
12828 }
12829
12830 void
12831 BackwardInner(target)
12832      int target;
12833 {
12834     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12835
12836     if (appData.debugMode)
12837         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12838                 target, currentMove, forwardMostMove);
12839
12840     if (gameMode == EditPosition) return;
12841     if (currentMove <= backwardMostMove) {
12842         ClearHighlights();
12843         DrawPosition(full_redraw, boards[currentMove]);
12844         return;
12845     }
12846     if (gameMode == PlayFromGameFile && !pausing)
12847       PauseEvent();
12848
12849     if (moveList[target][0]) {
12850         int fromX, fromY, toX, toY;
12851         toX = moveList[target][2] - AAA;
12852         toY = moveList[target][3] - ONE;
12853         if (moveList[target][1] == '@') {
12854             if (appData.highlightLastMove) {
12855                 SetHighlights(-1, -1, toX, toY);
12856             }
12857         } else {
12858             fromX = moveList[target][0] - AAA;
12859             fromY = moveList[target][1] - ONE;
12860             if (target == currentMove - 1) {
12861                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12862             }
12863             if (appData.highlightLastMove) {
12864                 SetHighlights(fromX, fromY, toX, toY);
12865             }
12866         }
12867     }
12868     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12869         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12870         while (currentMove > target) {
12871             SendToProgram("undo\n", &first);
12872             currentMove--;
12873         }
12874     } else {
12875         currentMove = target;
12876     }
12877
12878     if (gameMode == EditGame || gameMode == EndOfGame) {
12879         whiteTimeRemaining = timeRemaining[0][currentMove];
12880         blackTimeRemaining = timeRemaining[1][currentMove];
12881     }
12882     DisplayBothClocks();
12883     DisplayMove(currentMove - 1);
12884     DrawPosition(full_redraw, boards[currentMove]);
12885     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12886     // [HGM] PV info: routine tests if comment empty
12887     DisplayComment(currentMove - 1, commentList[currentMove]);
12888 }
12889
12890 void
12891 BackwardEvent()
12892 {
12893     if (gameMode == IcsExamining && !pausing) {
12894         SendToICS(ics_prefix);
12895         SendToICS("backward\n");
12896     } else {
12897         BackwardInner(currentMove - 1);
12898     }
12899 }
12900
12901 void
12902 ToStartEvent()
12903 {
12904     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12905         /* to optimize, we temporarily turn off analysis mode while we undo
12906          * all the moves. Otherwise we get analysis output after each undo.
12907          */
12908         if (first.analysisSupport) {
12909           SendToProgram("exit\nforce\n", &first);
12910           first.analyzing = FALSE;
12911         }
12912     }
12913
12914     if (gameMode == IcsExamining && !pausing) {
12915         SendToICS(ics_prefix);
12916         SendToICS("backward 999999\n");
12917     } else {
12918         BackwardInner(backwardMostMove);
12919     }
12920
12921     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12922         /* we have fed all the moves, so reactivate analysis mode */
12923         SendToProgram("analyze\n", &first);
12924         first.analyzing = TRUE;
12925         /*first.maybeThinking = TRUE;*/
12926         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12927     }
12928 }
12929
12930 void
12931 ToNrEvent(int to)
12932 {
12933   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12934   if (to >= forwardMostMove) to = forwardMostMove;
12935   if (to <= backwardMostMove) to = backwardMostMove;
12936   if (to < currentMove) {
12937     BackwardInner(to);
12938   } else {
12939     ForwardInner(to);
12940   }
12941 }
12942
12943 void
12944 RevertEvent(Boolean annotate)
12945 {
12946     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12947         return;
12948     }
12949     if (gameMode != IcsExamining) {
12950         DisplayError(_("You are not examining a game"), 0);
12951         return;
12952     }
12953     if (pausing) {
12954         DisplayError(_("You can't revert while pausing"), 0);
12955         return;
12956     }
12957     SendToICS(ics_prefix);
12958     SendToICS("revert\n");
12959 }
12960
12961 void
12962 RetractMoveEvent()
12963 {
12964     switch (gameMode) {
12965       case MachinePlaysWhite:
12966       case MachinePlaysBlack:
12967         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12968             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12969             return;
12970         }
12971         if (forwardMostMove < 2) return;
12972         currentMove = forwardMostMove = forwardMostMove - 2;
12973         whiteTimeRemaining = timeRemaining[0][currentMove];
12974         blackTimeRemaining = timeRemaining[1][currentMove];
12975         DisplayBothClocks();
12976         DisplayMove(currentMove - 1);
12977         ClearHighlights();/*!! could figure this out*/
12978         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12979         SendToProgram("remove\n", &first);
12980         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12981         break;
12982
12983       case BeginningOfGame:
12984       default:
12985         break;
12986
12987       case IcsPlayingWhite:
12988       case IcsPlayingBlack:
12989         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12990             SendToICS(ics_prefix);
12991             SendToICS("takeback 2\n");
12992         } else {
12993             SendToICS(ics_prefix);
12994             SendToICS("takeback 1\n");
12995         }
12996         break;
12997     }
12998 }
12999
13000 void
13001 MoveNowEvent()
13002 {
13003     ChessProgramState *cps;
13004
13005     switch (gameMode) {
13006       case MachinePlaysWhite:
13007         if (!WhiteOnMove(forwardMostMove)) {
13008             DisplayError(_("It is your turn"), 0);
13009             return;
13010         }
13011         cps = &first;
13012         break;
13013       case MachinePlaysBlack:
13014         if (WhiteOnMove(forwardMostMove)) {
13015             DisplayError(_("It is your turn"), 0);
13016             return;
13017         }
13018         cps = &first;
13019         break;
13020       case TwoMachinesPlay:
13021         if (WhiteOnMove(forwardMostMove) ==
13022             (first.twoMachinesColor[0] == 'w')) {
13023             cps = &first;
13024         } else {
13025             cps = &second;
13026         }
13027         break;
13028       case BeginningOfGame:
13029       default:
13030         return;
13031     }
13032     SendToProgram("?\n", cps);
13033 }
13034
13035 void
13036 TruncateGameEvent()
13037 {
13038     EditGameEvent();
13039     if (gameMode != EditGame) return;
13040     TruncateGame();
13041 }
13042
13043 void
13044 TruncateGame()
13045 {
13046     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13047     if (forwardMostMove > currentMove) {
13048         if (gameInfo.resultDetails != NULL) {
13049             free(gameInfo.resultDetails);
13050             gameInfo.resultDetails = NULL;
13051             gameInfo.result = GameUnfinished;
13052         }
13053         forwardMostMove = currentMove;
13054         HistorySet(parseList, backwardMostMove, forwardMostMove,
13055                    currentMove-1);
13056     }
13057 }
13058
13059 void
13060 HintEvent()
13061 {
13062     if (appData.noChessProgram) return;
13063     switch (gameMode) {
13064       case MachinePlaysWhite:
13065         if (WhiteOnMove(forwardMostMove)) {
13066             DisplayError(_("Wait until your turn"), 0);
13067             return;
13068         }
13069         break;
13070       case BeginningOfGame:
13071       case MachinePlaysBlack:
13072         if (!WhiteOnMove(forwardMostMove)) {
13073             DisplayError(_("Wait until your turn"), 0);
13074             return;
13075         }
13076         break;
13077       default:
13078         DisplayError(_("No hint available"), 0);
13079         return;
13080     }
13081     SendToProgram("hint\n", &first);
13082     hintRequested = TRUE;
13083 }
13084
13085 void
13086 BookEvent()
13087 {
13088     if (appData.noChessProgram) return;
13089     switch (gameMode) {
13090       case MachinePlaysWhite:
13091         if (WhiteOnMove(forwardMostMove)) {
13092             DisplayError(_("Wait until your turn"), 0);
13093             return;
13094         }
13095         break;
13096       case BeginningOfGame:
13097       case MachinePlaysBlack:
13098         if (!WhiteOnMove(forwardMostMove)) {
13099             DisplayError(_("Wait until your turn"), 0);
13100             return;
13101         }
13102         break;
13103       case EditPosition:
13104         EditPositionDone(TRUE);
13105         break;
13106       case TwoMachinesPlay:
13107         return;
13108       default:
13109         break;
13110     }
13111     SendToProgram("bk\n", &first);
13112     bookOutput[0] = NULLCHAR;
13113     bookRequested = TRUE;
13114 }
13115
13116 void
13117 AboutGameEvent()
13118 {
13119     char *tags = PGNTags(&gameInfo);
13120     TagsPopUp(tags, CmailMsg());
13121     free(tags);
13122 }
13123
13124 /* end button procedures */
13125
13126 void
13127 PrintPosition(fp, move)
13128      FILE *fp;
13129      int move;
13130 {
13131     int i, j;
13132
13133     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13134         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13135             char c = PieceToChar(boards[move][i][j]);
13136             fputc(c == 'x' ? '.' : c, fp);
13137             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13138         }
13139     }
13140     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13141       fprintf(fp, "white to play\n");
13142     else
13143       fprintf(fp, "black to play\n");
13144 }
13145
13146 void
13147 PrintOpponents(fp)
13148      FILE *fp;
13149 {
13150     if (gameInfo.white != NULL) {
13151         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13152     } else {
13153         fprintf(fp, "\n");
13154     }
13155 }
13156
13157 /* Find last component of program's own name, using some heuristics */
13158 void
13159 TidyProgramName(prog, host, buf)
13160      char *prog, *host, buf[MSG_SIZ];
13161 {
13162     char *p, *q;
13163     int local = (strcmp(host, "localhost") == 0);
13164     while (!local && (p = strchr(prog, ';')) != NULL) {
13165         p++;
13166         while (*p == ' ') p++;
13167         prog = p;
13168     }
13169     if (*prog == '"' || *prog == '\'') {
13170         q = strchr(prog + 1, *prog);
13171     } else {
13172         q = strchr(prog, ' ');
13173     }
13174     if (q == NULL) q = prog + strlen(prog);
13175     p = q;
13176     while (p >= prog && *p != '/' && *p != '\\') p--;
13177     p++;
13178     if(p == prog && *p == '"') p++;
13179     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13180     memcpy(buf, p, q - p);
13181     buf[q - p] = NULLCHAR;
13182     if (!local) {
13183         strcat(buf, "@");
13184         strcat(buf, host);
13185     }
13186 }
13187
13188 char *
13189 TimeControlTagValue()
13190 {
13191     char buf[MSG_SIZ];
13192     if (!appData.clockMode) {
13193       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13194     } else if (movesPerSession > 0) {
13195       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13196     } else if (timeIncrement == 0) {
13197       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13198     } else {
13199       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13200     }
13201     return StrSave(buf);
13202 }
13203
13204 void
13205 SetGameInfo()
13206 {
13207     /* This routine is used only for certain modes */
13208     VariantClass v = gameInfo.variant;
13209     ChessMove r = GameUnfinished;
13210     char *p = NULL;
13211
13212     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13213         r = gameInfo.result;
13214         p = gameInfo.resultDetails;
13215         gameInfo.resultDetails = NULL;
13216     }
13217     ClearGameInfo(&gameInfo);
13218     gameInfo.variant = v;
13219
13220     switch (gameMode) {
13221       case MachinePlaysWhite:
13222         gameInfo.event = StrSave( appData.pgnEventHeader );
13223         gameInfo.site = StrSave(HostName());
13224         gameInfo.date = PGNDate();
13225         gameInfo.round = StrSave("-");
13226         gameInfo.white = StrSave(first.tidy);
13227         gameInfo.black = StrSave(UserName());
13228         gameInfo.timeControl = TimeControlTagValue();
13229         break;
13230
13231       case MachinePlaysBlack:
13232         gameInfo.event = StrSave( appData.pgnEventHeader );
13233         gameInfo.site = StrSave(HostName());
13234         gameInfo.date = PGNDate();
13235         gameInfo.round = StrSave("-");
13236         gameInfo.white = StrSave(UserName());
13237         gameInfo.black = StrSave(first.tidy);
13238         gameInfo.timeControl = TimeControlTagValue();
13239         break;
13240
13241       case TwoMachinesPlay:
13242         gameInfo.event = StrSave( appData.pgnEventHeader );
13243         gameInfo.site = StrSave(HostName());
13244         gameInfo.date = PGNDate();
13245         if (matchGame > 0) {
13246             char buf[MSG_SIZ];
13247             snprintf(buf, MSG_SIZ, "%d", matchGame);
13248             gameInfo.round = StrSave(buf);
13249         } else {
13250             gameInfo.round = StrSave("-");
13251         }
13252         if (first.twoMachinesColor[0] == 'w') {
13253             gameInfo.white = StrSave(first.tidy);
13254             gameInfo.black = StrSave(second.tidy);
13255         } else {
13256             gameInfo.white = StrSave(second.tidy);
13257             gameInfo.black = StrSave(first.tidy);
13258         }
13259         gameInfo.timeControl = TimeControlTagValue();
13260         break;
13261
13262       case EditGame:
13263         gameInfo.event = StrSave("Edited game");
13264         gameInfo.site = StrSave(HostName());
13265         gameInfo.date = PGNDate();
13266         gameInfo.round = StrSave("-");
13267         gameInfo.white = StrSave("-");
13268         gameInfo.black = StrSave("-");
13269         gameInfo.result = r;
13270         gameInfo.resultDetails = p;
13271         break;
13272
13273       case EditPosition:
13274         gameInfo.event = StrSave("Edited position");
13275         gameInfo.site = StrSave(HostName());
13276         gameInfo.date = PGNDate();
13277         gameInfo.round = StrSave("-");
13278         gameInfo.white = StrSave("-");
13279         gameInfo.black = StrSave("-");
13280         break;
13281
13282       case IcsPlayingWhite:
13283       case IcsPlayingBlack:
13284       case IcsObserving:
13285       case IcsExamining:
13286         break;
13287
13288       case PlayFromGameFile:
13289         gameInfo.event = StrSave("Game from non-PGN file");
13290         gameInfo.site = StrSave(HostName());
13291         gameInfo.date = PGNDate();
13292         gameInfo.round = StrSave("-");
13293         gameInfo.white = StrSave("?");
13294         gameInfo.black = StrSave("?");
13295         break;
13296
13297       default:
13298         break;
13299     }
13300 }
13301
13302 void
13303 ReplaceComment(index, text)
13304      int index;
13305      char *text;
13306 {
13307     int len;
13308
13309     while (*text == '\n') text++;
13310     len = strlen(text);
13311     while (len > 0 && text[len - 1] == '\n') len--;
13312
13313     if (commentList[index] != NULL)
13314       free(commentList[index]);
13315
13316     if (len == 0) {
13317         commentList[index] = NULL;
13318         return;
13319     }
13320   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13321       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13322       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13323     commentList[index] = (char *) malloc(len + 2);
13324     strncpy(commentList[index], text, len);
13325     commentList[index][len] = '\n';
13326     commentList[index][len + 1] = NULLCHAR;
13327   } else {
13328     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13329     char *p;
13330     commentList[index] = (char *) malloc(len + 7);
13331     safeStrCpy(commentList[index], "{\n", 3);
13332     safeStrCpy(commentList[index]+2, text, len+1);
13333     commentList[index][len+2] = NULLCHAR;
13334     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13335     strcat(commentList[index], "\n}\n");
13336   }
13337 }
13338
13339 void
13340 CrushCRs(text)
13341      char *text;
13342 {
13343   char *p = text;
13344   char *q = text;
13345   char ch;
13346
13347   do {
13348     ch = *p++;
13349     if (ch == '\r') continue;
13350     *q++ = ch;
13351   } while (ch != '\0');
13352 }
13353
13354 void
13355 AppendComment(index, text, addBraces)
13356      int index;
13357      char *text;
13358      Boolean addBraces; // [HGM] braces: tells if we should add {}
13359 {
13360     int oldlen, len;
13361     char *old;
13362
13363 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13364     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13365
13366     CrushCRs(text);
13367     while (*text == '\n') text++;
13368     len = strlen(text);
13369     while (len > 0 && text[len - 1] == '\n') len--;
13370
13371     if (len == 0) return;
13372
13373     if (commentList[index] != NULL) {
13374         old = commentList[index];
13375         oldlen = strlen(old);
13376         while(commentList[index][oldlen-1] ==  '\n')
13377           commentList[index][--oldlen] = NULLCHAR;
13378         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13379         safeStrCpy(commentList[index], old, oldlen + len + 6);
13380         free(old);
13381         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13382         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13383           if(addBraces) addBraces = FALSE; else { text++; len--; }
13384           while (*text == '\n') { text++; len--; }
13385           commentList[index][--oldlen] = NULLCHAR;
13386       }
13387         if(addBraces) strcat(commentList[index], "\n{\n");
13388         else          strcat(commentList[index], "\n");
13389         strcat(commentList[index], text);
13390         if(addBraces) strcat(commentList[index], "\n}\n");
13391         else          strcat(commentList[index], "\n");
13392     } else {
13393         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13394         if(addBraces)
13395           safeStrCpy(commentList[index], "{\n", 3);
13396         else commentList[index][0] = NULLCHAR;
13397         strcat(commentList[index], text);
13398         strcat(commentList[index], "\n");
13399         if(addBraces) strcat(commentList[index], "}\n");
13400     }
13401 }
13402
13403 static char * FindStr( char * text, char * sub_text )
13404 {
13405     char * result = strstr( text, sub_text );
13406
13407     if( result != NULL ) {
13408         result += strlen( sub_text );
13409     }
13410
13411     return result;
13412 }
13413
13414 /* [AS] Try to extract PV info from PGN comment */
13415 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13416 char *GetInfoFromComment( int index, char * text )
13417 {
13418     char * sep = text;
13419
13420     if( text != NULL && index > 0 ) {
13421         int score = 0;
13422         int depth = 0;
13423         int time = -1, sec = 0, deci;
13424         char * s_eval = FindStr( text, "[%eval " );
13425         char * s_emt = FindStr( text, "[%emt " );
13426
13427         if( s_eval != NULL || s_emt != NULL ) {
13428             /* New style */
13429             char delim;
13430
13431             if( s_eval != NULL ) {
13432                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13433                     return text;
13434                 }
13435
13436                 if( delim != ']' ) {
13437                     return text;
13438                 }
13439             }
13440
13441             if( s_emt != NULL ) {
13442             }
13443                 return text;
13444         }
13445         else {
13446             /* We expect something like: [+|-]nnn.nn/dd */
13447             int score_lo = 0;
13448
13449             if(*text != '{') return text; // [HGM] braces: must be normal comment
13450
13451             sep = strchr( text, '/' );
13452             if( sep == NULL || sep < (text+4) ) {
13453                 return text;
13454             }
13455
13456             time = -1; sec = -1; deci = -1;
13457             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13458                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13459                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13460                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13461                 return text;
13462             }
13463
13464             if( score_lo < 0 || score_lo >= 100 ) {
13465                 return text;
13466             }
13467
13468             if(sec >= 0) time = 600*time + 10*sec; else
13469             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13470
13471             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13472
13473             /* [HGM] PV time: now locate end of PV info */
13474             while( *++sep >= '0' && *sep <= '9'); // strip depth
13475             if(time >= 0)
13476             while( *++sep >= '0' && *sep <= '9'); // strip time
13477             if(sec >= 0)
13478             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13479             if(deci >= 0)
13480             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13481             while(*sep == ' ') sep++;
13482         }
13483
13484         if( depth <= 0 ) {
13485             return text;
13486         }
13487
13488         if( time < 0 ) {
13489             time = -1;
13490         }
13491
13492         pvInfoList[index-1].depth = depth;
13493         pvInfoList[index-1].score = score;
13494         pvInfoList[index-1].time  = 10*time; // centi-sec
13495         if(*sep == '}') *sep = 0; else *--sep = '{';
13496     }
13497     return sep;
13498 }
13499
13500 void
13501 SendToProgram(message, cps)
13502      char *message;
13503      ChessProgramState *cps;
13504 {
13505     int count, outCount, error;
13506     char buf[MSG_SIZ];
13507
13508     if (cps->pr == NULL) return;
13509     Attention(cps);
13510
13511     if (appData.debugMode) {
13512         TimeMark now;
13513         GetTimeMark(&now);
13514         fprintf(debugFP, "%ld >%-6s: %s",
13515                 SubtractTimeMarks(&now, &programStartTime),
13516                 cps->which, message);
13517     }
13518
13519     count = strlen(message);
13520     outCount = OutputToProcess(cps->pr, message, count, &error);
13521     if (outCount < count && !exiting
13522                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13523       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13524         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13525             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13526                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13527                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13528             } else {
13529                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13530             }
13531             gameInfo.resultDetails = StrSave(buf);
13532         }
13533         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13534     }
13535 }
13536
13537 void
13538 ReceiveFromProgram(isr, closure, message, count, error)
13539      InputSourceRef isr;
13540      VOIDSTAR closure;
13541      char *message;
13542      int count;
13543      int error;
13544 {
13545     char *end_str;
13546     char buf[MSG_SIZ];
13547     ChessProgramState *cps = (ChessProgramState *)closure;
13548
13549     if (isr != cps->isr) return; /* Killed intentionally */
13550     if (count <= 0) {
13551         if (count == 0) {
13552             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13553                     cps->which, cps->program);
13554         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13555                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13556                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13557                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13558                 } else {
13559                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13560                 }
13561                 gameInfo.resultDetails = StrSave(buf);
13562             }
13563             RemoveInputSource(cps->isr);
13564             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13565         } else {
13566             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13567                     cps->which, cps->program);
13568             RemoveInputSource(cps->isr);
13569
13570             /* [AS] Program is misbehaving badly... kill it */
13571             if( count == -2 ) {
13572                 DestroyChildProcess( cps->pr, 9 );
13573                 cps->pr = NoProc;
13574             }
13575
13576             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13577         }
13578         return;
13579     }
13580
13581     if ((end_str = strchr(message, '\r')) != NULL)
13582       *end_str = NULLCHAR;
13583     if ((end_str = strchr(message, '\n')) != NULL)
13584       *end_str = NULLCHAR;
13585
13586     if (appData.debugMode) {
13587         TimeMark now; int print = 1;
13588         char *quote = ""; char c; int i;
13589
13590         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13591                 char start = message[0];
13592                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13593                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13594                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13595                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13596                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13597                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13598                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13599                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13600                    sscanf(message, "hint: %c", &c)!=1 && 
13601                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13602                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13603                     print = (appData.engineComments >= 2);
13604                 }
13605                 message[0] = start; // restore original message
13606         }
13607         if(print) {
13608                 GetTimeMark(&now);
13609                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13610                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13611                         quote,
13612                         message);
13613         }
13614     }
13615
13616     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13617     if (appData.icsEngineAnalyze) {
13618         if (strstr(message, "whisper") != NULL ||
13619              strstr(message, "kibitz") != NULL ||
13620             strstr(message, "tellics") != NULL) return;
13621     }
13622
13623     HandleMachineMove(message, cps);
13624 }
13625
13626
13627 void
13628 SendTimeControl(cps, mps, tc, inc, sd, st)
13629      ChessProgramState *cps;
13630      int mps, inc, sd, st;
13631      long tc;
13632 {
13633     char buf[MSG_SIZ];
13634     int seconds;
13635
13636     if( timeControl_2 > 0 ) {
13637         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13638             tc = timeControl_2;
13639         }
13640     }
13641     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13642     inc /= cps->timeOdds;
13643     st  /= cps->timeOdds;
13644
13645     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13646
13647     if (st > 0) {
13648       /* Set exact time per move, normally using st command */
13649       if (cps->stKludge) {
13650         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13651         seconds = st % 60;
13652         if (seconds == 0) {
13653           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13654         } else {
13655           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13656         }
13657       } else {
13658         snprintf(buf, MSG_SIZ, "st %d\n", st);
13659       }
13660     } else {
13661       /* Set conventional or incremental time control, using level command */
13662       if (seconds == 0) {
13663         /* Note old gnuchess bug -- minutes:seconds used to not work.
13664            Fixed in later versions, but still avoid :seconds
13665            when seconds is 0. */
13666         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13667       } else {
13668         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13669                  seconds, inc/1000.);
13670       }
13671     }
13672     SendToProgram(buf, cps);
13673
13674     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13675     /* Orthogonally, limit search to given depth */
13676     if (sd > 0) {
13677       if (cps->sdKludge) {
13678         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13679       } else {
13680         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13681       }
13682       SendToProgram(buf, cps);
13683     }
13684
13685     if(cps->nps > 0) { /* [HGM] nps */
13686         if(cps->supportsNPS == FALSE)
13687           cps->nps = -1; // don't use if engine explicitly says not supported!
13688         else {
13689           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13690           SendToProgram(buf, cps);
13691         }
13692     }
13693 }
13694
13695 ChessProgramState *WhitePlayer()
13696 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13697 {
13698     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13699        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13700         return &second;
13701     return &first;
13702 }
13703
13704 void
13705 SendTimeRemaining(cps, machineWhite)
13706      ChessProgramState *cps;
13707      int /*boolean*/ machineWhite;
13708 {
13709     char message[MSG_SIZ];
13710     long time, otime;
13711
13712     /* Note: this routine must be called when the clocks are stopped
13713        or when they have *just* been set or switched; otherwise
13714        it will be off by the time since the current tick started.
13715     */
13716     if (machineWhite) {
13717         time = whiteTimeRemaining / 10;
13718         otime = blackTimeRemaining / 10;
13719     } else {
13720         time = blackTimeRemaining / 10;
13721         otime = whiteTimeRemaining / 10;
13722     }
13723     /* [HGM] translate opponent's time by time-odds factor */
13724     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13725     if (appData.debugMode) {
13726         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13727     }
13728
13729     if (time <= 0) time = 1;
13730     if (otime <= 0) otime = 1;
13731
13732     snprintf(message, MSG_SIZ, "time %ld\n", time);
13733     SendToProgram(message, cps);
13734
13735     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13736     SendToProgram(message, cps);
13737 }
13738
13739 int
13740 BoolFeature(p, name, loc, cps)
13741      char **p;
13742      char *name;
13743      int *loc;
13744      ChessProgramState *cps;
13745 {
13746   char buf[MSG_SIZ];
13747   int len = strlen(name);
13748   int val;
13749
13750   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13751     (*p) += len + 1;
13752     sscanf(*p, "%d", &val);
13753     *loc = (val != 0);
13754     while (**p && **p != ' ')
13755       (*p)++;
13756     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13757     SendToProgram(buf, cps);
13758     return TRUE;
13759   }
13760   return FALSE;
13761 }
13762
13763 int
13764 IntFeature(p, name, loc, cps)
13765      char **p;
13766      char *name;
13767      int *loc;
13768      ChessProgramState *cps;
13769 {
13770   char buf[MSG_SIZ];
13771   int len = strlen(name);
13772   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13773     (*p) += len + 1;
13774     sscanf(*p, "%d", loc);
13775     while (**p && **p != ' ') (*p)++;
13776     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13777     SendToProgram(buf, cps);
13778     return TRUE;
13779   }
13780   return FALSE;
13781 }
13782
13783 int
13784 StringFeature(p, name, loc, cps)
13785      char **p;
13786      char *name;
13787      char loc[];
13788      ChessProgramState *cps;
13789 {
13790   char buf[MSG_SIZ];
13791   int len = strlen(name);
13792   if (strncmp((*p), name, len) == 0
13793       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13794     (*p) += len + 2;
13795     sscanf(*p, "%[^\"]", loc);
13796     while (**p && **p != '\"') (*p)++;
13797     if (**p == '\"') (*p)++;
13798     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13799     SendToProgram(buf, cps);
13800     return TRUE;
13801   }
13802   return FALSE;
13803 }
13804
13805 int
13806 ParseOption(Option *opt, ChessProgramState *cps)
13807 // [HGM] options: process the string that defines an engine option, and determine
13808 // name, type, default value, and allowed value range
13809 {
13810         char *p, *q, buf[MSG_SIZ];
13811         int n, min = (-1)<<31, max = 1<<31, def;
13812
13813         if(p = strstr(opt->name, " -spin ")) {
13814             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13815             if(max < min) max = min; // enforce consistency
13816             if(def < min) def = min;
13817             if(def > max) def = max;
13818             opt->value = def;
13819             opt->min = min;
13820             opt->max = max;
13821             opt->type = Spin;
13822         } else if((p = strstr(opt->name, " -slider "))) {
13823             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13824             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13825             if(max < min) max = min; // enforce consistency
13826             if(def < min) def = min;
13827             if(def > max) def = max;
13828             opt->value = def;
13829             opt->min = min;
13830             opt->max = max;
13831             opt->type = Spin; // Slider;
13832         } else if((p = strstr(opt->name, " -string "))) {
13833             opt->textValue = p+9;
13834             opt->type = TextBox;
13835         } else if((p = strstr(opt->name, " -file "))) {
13836             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13837             opt->textValue = p+7;
13838             opt->type = TextBox; // FileName;
13839         } else if((p = strstr(opt->name, " -path "))) {
13840             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13841             opt->textValue = p+7;
13842             opt->type = TextBox; // PathName;
13843         } else if(p = strstr(opt->name, " -check ")) {
13844             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13845             opt->value = (def != 0);
13846             opt->type = CheckBox;
13847         } else if(p = strstr(opt->name, " -combo ")) {
13848             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13849             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13850             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13851             opt->value = n = 0;
13852             while(q = StrStr(q, " /// ")) {
13853                 n++; *q = 0;    // count choices, and null-terminate each of them
13854                 q += 5;
13855                 if(*q == '*') { // remember default, which is marked with * prefix
13856                     q++;
13857                     opt->value = n;
13858                 }
13859                 cps->comboList[cps->comboCnt++] = q;
13860             }
13861             cps->comboList[cps->comboCnt++] = NULL;
13862             opt->max = n + 1;
13863             opt->type = ComboBox;
13864         } else if(p = strstr(opt->name, " -button")) {
13865             opt->type = Button;
13866         } else if(p = strstr(opt->name, " -save")) {
13867             opt->type = SaveButton;
13868         } else return FALSE;
13869         *p = 0; // terminate option name
13870         // now look if the command-line options define a setting for this engine option.
13871         if(cps->optionSettings && cps->optionSettings[0])
13872             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13873         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13874           snprintf(buf, MSG_SIZ, "option %s", p);
13875                 if(p = strstr(buf, ",")) *p = 0;
13876                 if(q = strchr(buf, '=')) switch(opt->type) {
13877                     case ComboBox:
13878                         for(n=0; n<opt->max; n++)
13879                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13880                         break;
13881                     case TextBox:
13882                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13883                         break;
13884                     case Spin:
13885                     case CheckBox:
13886                         opt->value = atoi(q+1);
13887                     default:
13888                         break;
13889                 }
13890                 strcat(buf, "\n");
13891                 SendToProgram(buf, cps);
13892         }
13893         return TRUE;
13894 }
13895
13896 void
13897 FeatureDone(cps, val)
13898      ChessProgramState* cps;
13899      int val;
13900 {
13901   DelayedEventCallback cb = GetDelayedEvent();
13902   if ((cb == InitBackEnd3 && cps == &first) ||
13903       (cb == SettingsMenuIfReady && cps == &second) ||
13904       (cb == TwoMachinesEventIfReady && cps == &second)) {
13905     CancelDelayedEvent();
13906     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13907   }
13908   cps->initDone = val;
13909 }
13910
13911 /* Parse feature command from engine */
13912 void
13913 ParseFeatures(args, cps)
13914      char* args;
13915      ChessProgramState *cps;
13916 {
13917   char *p = args;
13918   char *q;
13919   int val;
13920   char buf[MSG_SIZ];
13921
13922   for (;;) {
13923     while (*p == ' ') p++;
13924     if (*p == NULLCHAR) return;
13925
13926     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13927     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13928     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13929     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13930     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13931     if (BoolFeature(&p, "reuse", &val, cps)) {
13932       /* Engine can disable reuse, but can't enable it if user said no */
13933       if (!val) cps->reuse = FALSE;
13934       continue;
13935     }
13936     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13937     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13938       if (gameMode == TwoMachinesPlay) {
13939         DisplayTwoMachinesTitle();
13940       } else {
13941         DisplayTitle("");
13942       }
13943       continue;
13944     }
13945     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13946     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13947     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13948     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13949     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13950     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13951     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13952     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13953     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13954     if (IntFeature(&p, "done", &val, cps)) {
13955       FeatureDone(cps, val);
13956       continue;
13957     }
13958     /* Added by Tord: */
13959     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13960     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13961     /* End of additions by Tord */
13962
13963     /* [HGM] added features: */
13964     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13965     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13966     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13967     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13968     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13969     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13970     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13971         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13972           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13973             SendToProgram(buf, cps);
13974             continue;
13975         }
13976         if(cps->nrOptions >= MAX_OPTIONS) {
13977             cps->nrOptions--;
13978             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13979             DisplayError(buf, 0);
13980         }
13981         continue;
13982     }
13983     /* End of additions by HGM */
13984
13985     /* unknown feature: complain and skip */
13986     q = p;
13987     while (*q && *q != '=') q++;
13988     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13989     SendToProgram(buf, cps);
13990     p = q;
13991     if (*p == '=') {
13992       p++;
13993       if (*p == '\"') {
13994         p++;
13995         while (*p && *p != '\"') p++;
13996         if (*p == '\"') p++;
13997       } else {
13998         while (*p && *p != ' ') p++;
13999       }
14000     }
14001   }
14002
14003 }
14004
14005 void
14006 PeriodicUpdatesEvent(newState)
14007      int newState;
14008 {
14009     if (newState == appData.periodicUpdates)
14010       return;
14011
14012     appData.periodicUpdates=newState;
14013
14014     /* Display type changes, so update it now */
14015 //    DisplayAnalysis();
14016
14017     /* Get the ball rolling again... */
14018     if (newState) {
14019         AnalysisPeriodicEvent(1);
14020         StartAnalysisClock();
14021     }
14022 }
14023
14024 void
14025 PonderNextMoveEvent(newState)
14026      int newState;
14027 {
14028     if (newState == appData.ponderNextMove) return;
14029     if (gameMode == EditPosition) EditPositionDone(TRUE);
14030     if (newState) {
14031         SendToProgram("hard\n", &first);
14032         if (gameMode == TwoMachinesPlay) {
14033             SendToProgram("hard\n", &second);
14034         }
14035     } else {
14036         SendToProgram("easy\n", &first);
14037         thinkOutput[0] = NULLCHAR;
14038         if (gameMode == TwoMachinesPlay) {
14039             SendToProgram("easy\n", &second);
14040         }
14041     }
14042     appData.ponderNextMove = newState;
14043 }
14044
14045 void
14046 NewSettingEvent(option, feature, command, value)
14047      char *command;
14048      int option, value, *feature;
14049 {
14050     char buf[MSG_SIZ];
14051
14052     if (gameMode == EditPosition) EditPositionDone(TRUE);
14053     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14054     if(feature == NULL || *feature) SendToProgram(buf, &first);
14055     if (gameMode == TwoMachinesPlay) {
14056         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14057     }
14058 }
14059
14060 void
14061 ShowThinkingEvent()
14062 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14063 {
14064     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14065     int newState = appData.showThinking
14066         // [HGM] thinking: other features now need thinking output as well
14067         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14068
14069     if (oldState == newState) return;
14070     oldState = newState;
14071     if (gameMode == EditPosition) EditPositionDone(TRUE);
14072     if (oldState) {
14073         SendToProgram("post\n", &first);
14074         if (gameMode == TwoMachinesPlay) {
14075             SendToProgram("post\n", &second);
14076         }
14077     } else {
14078         SendToProgram("nopost\n", &first);
14079         thinkOutput[0] = NULLCHAR;
14080         if (gameMode == TwoMachinesPlay) {
14081             SendToProgram("nopost\n", &second);
14082         }
14083     }
14084 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14085 }
14086
14087 void
14088 AskQuestionEvent(title, question, replyPrefix, which)
14089      char *title; char *question; char *replyPrefix; char *which;
14090 {
14091   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14092   if (pr == NoProc) return;
14093   AskQuestion(title, question, replyPrefix, pr);
14094 }
14095
14096 void
14097 DisplayMove(moveNumber)
14098      int moveNumber;
14099 {
14100     char message[MSG_SIZ];
14101     char res[MSG_SIZ];
14102     char cpThinkOutput[MSG_SIZ];
14103
14104     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14105
14106     if (moveNumber == forwardMostMove - 1 ||
14107         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14108
14109         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14110
14111         if (strchr(cpThinkOutput, '\n')) {
14112             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14113         }
14114     } else {
14115         *cpThinkOutput = NULLCHAR;
14116     }
14117
14118     /* [AS] Hide thinking from human user */
14119     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14120         *cpThinkOutput = NULLCHAR;
14121         if( thinkOutput[0] != NULLCHAR ) {
14122             int i;
14123
14124             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14125                 cpThinkOutput[i] = '.';
14126             }
14127             cpThinkOutput[i] = NULLCHAR;
14128             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14129         }
14130     }
14131
14132     if (moveNumber == forwardMostMove - 1 &&
14133         gameInfo.resultDetails != NULL) {
14134         if (gameInfo.resultDetails[0] == NULLCHAR) {
14135           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14136         } else {
14137           snprintf(res, MSG_SIZ, " {%s} %s",
14138                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14139         }
14140     } else {
14141         res[0] = NULLCHAR;
14142     }
14143
14144     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14145         DisplayMessage(res, cpThinkOutput);
14146     } else {
14147       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14148                 WhiteOnMove(moveNumber) ? " " : ".. ",
14149                 parseList[moveNumber], res);
14150         DisplayMessage(message, cpThinkOutput);
14151     }
14152 }
14153
14154 void
14155 DisplayComment(moveNumber, text)
14156      int moveNumber;
14157      char *text;
14158 {
14159     char title[MSG_SIZ];
14160     char buf[8000]; // comment can be long!
14161     int score, depth;
14162
14163     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14164       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14165     } else {
14166       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14167               WhiteOnMove(moveNumber) ? " " : ".. ",
14168               parseList[moveNumber]);
14169     }
14170     // [HGM] PV info: display PV info together with (or as) comment
14171     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14172       if(text == NULL) text = "";
14173       score = pvInfoList[moveNumber].score;
14174       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14175               depth, (pvInfoList[moveNumber].time+50)/100, text);
14176       text = buf;
14177     }
14178     if (text != NULL && (appData.autoDisplayComment || commentUp))
14179         CommentPopUp(title, text);
14180 }
14181
14182 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14183  * might be busy thinking or pondering.  It can be omitted if your
14184  * gnuchess is configured to stop thinking immediately on any user
14185  * input.  However, that gnuchess feature depends on the FIONREAD
14186  * ioctl, which does not work properly on some flavors of Unix.
14187  */
14188 void
14189 Attention(cps)
14190      ChessProgramState *cps;
14191 {
14192 #if ATTENTION
14193     if (!cps->useSigint) return;
14194     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14195     switch (gameMode) {
14196       case MachinePlaysWhite:
14197       case MachinePlaysBlack:
14198       case TwoMachinesPlay:
14199       case IcsPlayingWhite:
14200       case IcsPlayingBlack:
14201       case AnalyzeMode:
14202       case AnalyzeFile:
14203         /* Skip if we know it isn't thinking */
14204         if (!cps->maybeThinking) return;
14205         if (appData.debugMode)
14206           fprintf(debugFP, "Interrupting %s\n", cps->which);
14207         InterruptChildProcess(cps->pr);
14208         cps->maybeThinking = FALSE;
14209         break;
14210       default:
14211         break;
14212     }
14213 #endif /*ATTENTION*/
14214 }
14215
14216 int
14217 CheckFlags()
14218 {
14219     if (whiteTimeRemaining <= 0) {
14220         if (!whiteFlag) {
14221             whiteFlag = TRUE;
14222             if (appData.icsActive) {
14223                 if (appData.autoCallFlag &&
14224                     gameMode == IcsPlayingBlack && !blackFlag) {
14225                   SendToICS(ics_prefix);
14226                   SendToICS("flag\n");
14227                 }
14228             } else {
14229                 if (blackFlag) {
14230                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14231                 } else {
14232                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14233                     if (appData.autoCallFlag) {
14234                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14235                         return TRUE;
14236                     }
14237                 }
14238             }
14239         }
14240     }
14241     if (blackTimeRemaining <= 0) {
14242         if (!blackFlag) {
14243             blackFlag = TRUE;
14244             if (appData.icsActive) {
14245                 if (appData.autoCallFlag &&
14246                     gameMode == IcsPlayingWhite && !whiteFlag) {
14247                   SendToICS(ics_prefix);
14248                   SendToICS("flag\n");
14249                 }
14250             } else {
14251                 if (whiteFlag) {
14252                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14253                 } else {
14254                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14255                     if (appData.autoCallFlag) {
14256                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14257                         return TRUE;
14258                     }
14259                 }
14260             }
14261         }
14262     }
14263     return FALSE;
14264 }
14265
14266 void
14267 CheckTimeControl()
14268 {
14269     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14270         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14271
14272     /*
14273      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14274      */
14275     if ( !WhiteOnMove(forwardMostMove) ) {
14276         /* White made time control */
14277         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14278         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14279         /* [HGM] time odds: correct new time quota for time odds! */
14280                                             / WhitePlayer()->timeOdds;
14281         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14282     } else {
14283         lastBlack -= blackTimeRemaining;
14284         /* Black made time control */
14285         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14286                                             / WhitePlayer()->other->timeOdds;
14287         lastWhite = whiteTimeRemaining;
14288     }
14289 }
14290
14291 void
14292 DisplayBothClocks()
14293 {
14294     int wom = gameMode == EditPosition ?
14295       !blackPlaysFirst : WhiteOnMove(currentMove);
14296     DisplayWhiteClock(whiteTimeRemaining, wom);
14297     DisplayBlackClock(blackTimeRemaining, !wom);
14298 }
14299
14300
14301 /* Timekeeping seems to be a portability nightmare.  I think everyone
14302    has ftime(), but I'm really not sure, so I'm including some ifdefs
14303    to use other calls if you don't.  Clocks will be less accurate if
14304    you have neither ftime nor gettimeofday.
14305 */
14306
14307 /* VS 2008 requires the #include outside of the function */
14308 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14309 #include <sys/timeb.h>
14310 #endif
14311
14312 /* Get the current time as a TimeMark */
14313 void
14314 GetTimeMark(tm)
14315      TimeMark *tm;
14316 {
14317 #if HAVE_GETTIMEOFDAY
14318
14319     struct timeval timeVal;
14320     struct timezone timeZone;
14321
14322     gettimeofday(&timeVal, &timeZone);
14323     tm->sec = (long) timeVal.tv_sec;
14324     tm->ms = (int) (timeVal.tv_usec / 1000L);
14325
14326 #else /*!HAVE_GETTIMEOFDAY*/
14327 #if HAVE_FTIME
14328
14329 // include <sys/timeb.h> / moved to just above start of function
14330     struct timeb timeB;
14331
14332     ftime(&timeB);
14333     tm->sec = (long) timeB.time;
14334     tm->ms = (int) timeB.millitm;
14335
14336 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14337     tm->sec = (long) time(NULL);
14338     tm->ms = 0;
14339 #endif
14340 #endif
14341 }
14342
14343 /* Return the difference in milliseconds between two
14344    time marks.  We assume the difference will fit in a long!
14345 */
14346 long
14347 SubtractTimeMarks(tm2, tm1)
14348      TimeMark *tm2, *tm1;
14349 {
14350     return 1000L*(tm2->sec - tm1->sec) +
14351            (long) (tm2->ms - tm1->ms);
14352 }
14353
14354
14355 /*
14356  * Code to manage the game clocks.
14357  *
14358  * In tournament play, black starts the clock and then white makes a move.
14359  * We give the human user a slight advantage if he is playing white---the
14360  * clocks don't run until he makes his first move, so it takes zero time.
14361  * Also, we don't account for network lag, so we could get out of sync
14362  * with GNU Chess's clock -- but then, referees are always right.
14363  */
14364
14365 static TimeMark tickStartTM;
14366 static long intendedTickLength;
14367
14368 long
14369 NextTickLength(timeRemaining)
14370      long timeRemaining;
14371 {
14372     long nominalTickLength, nextTickLength;
14373
14374     if (timeRemaining > 0L && timeRemaining <= 10000L)
14375       nominalTickLength = 100L;
14376     else
14377       nominalTickLength = 1000L;
14378     nextTickLength = timeRemaining % nominalTickLength;
14379     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14380
14381     return nextTickLength;
14382 }
14383
14384 /* Adjust clock one minute up or down */
14385 void
14386 AdjustClock(Boolean which, int dir)
14387 {
14388     if(which) blackTimeRemaining += 60000*dir;
14389     else      whiteTimeRemaining += 60000*dir;
14390     DisplayBothClocks();
14391 }
14392
14393 /* Stop clocks and reset to a fresh time control */
14394 void
14395 ResetClocks()
14396 {
14397     (void) StopClockTimer();
14398     if (appData.icsActive) {
14399         whiteTimeRemaining = blackTimeRemaining = 0;
14400     } else if (searchTime) {
14401         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14402         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14403     } else { /* [HGM] correct new time quote for time odds */
14404         whiteTC = blackTC = fullTimeControlString;
14405         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14406         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14407     }
14408     if (whiteFlag || blackFlag) {
14409         DisplayTitle("");
14410         whiteFlag = blackFlag = FALSE;
14411     }
14412     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14413     DisplayBothClocks();
14414 }
14415
14416 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14417
14418 /* Decrement running clock by amount of time that has passed */
14419 void
14420 DecrementClocks()
14421 {
14422     long timeRemaining;
14423     long lastTickLength, fudge;
14424     TimeMark now;
14425
14426     if (!appData.clockMode) return;
14427     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14428
14429     GetTimeMark(&now);
14430
14431     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14432
14433     /* Fudge if we woke up a little too soon */
14434     fudge = intendedTickLength - lastTickLength;
14435     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14436
14437     if (WhiteOnMove(forwardMostMove)) {
14438         if(whiteNPS >= 0) lastTickLength = 0;
14439         timeRemaining = whiteTimeRemaining -= lastTickLength;
14440         if(timeRemaining < 0 && !appData.icsActive) {
14441             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14442             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14443                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14444                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14445             }
14446         }
14447         DisplayWhiteClock(whiteTimeRemaining - fudge,
14448                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14449     } else {
14450         if(blackNPS >= 0) lastTickLength = 0;
14451         timeRemaining = blackTimeRemaining -= lastTickLength;
14452         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14453             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14454             if(suddenDeath) {
14455                 blackStartMove = forwardMostMove;
14456                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14457             }
14458         }
14459         DisplayBlackClock(blackTimeRemaining - fudge,
14460                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14461     }
14462     if (CheckFlags()) return;
14463
14464     tickStartTM = now;
14465     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14466     StartClockTimer(intendedTickLength);
14467
14468     /* if the time remaining has fallen below the alarm threshold, sound the
14469      * alarm. if the alarm has sounded and (due to a takeback or time control
14470      * with increment) the time remaining has increased to a level above the
14471      * threshold, reset the alarm so it can sound again.
14472      */
14473
14474     if (appData.icsActive && appData.icsAlarm) {
14475
14476         /* make sure we are dealing with the user's clock */
14477         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14478                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14479            )) return;
14480
14481         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14482             alarmSounded = FALSE;
14483         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14484             PlayAlarmSound();
14485             alarmSounded = TRUE;
14486         }
14487     }
14488 }
14489
14490
14491 /* A player has just moved, so stop the previously running
14492    clock and (if in clock mode) start the other one.
14493    We redisplay both clocks in case we're in ICS mode, because
14494    ICS gives us an update to both clocks after every move.
14495    Note that this routine is called *after* forwardMostMove
14496    is updated, so the last fractional tick must be subtracted
14497    from the color that is *not* on move now.
14498 */
14499 void
14500 SwitchClocks(int newMoveNr)
14501 {
14502     long lastTickLength;
14503     TimeMark now;
14504     int flagged = FALSE;
14505
14506     GetTimeMark(&now);
14507
14508     if (StopClockTimer() && appData.clockMode) {
14509         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14510         if (!WhiteOnMove(forwardMostMove)) {
14511             if(blackNPS >= 0) lastTickLength = 0;
14512             blackTimeRemaining -= lastTickLength;
14513            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14514 //         if(pvInfoList[forwardMostMove-1].time == -1)
14515                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14516                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14517         } else {
14518            if(whiteNPS >= 0) lastTickLength = 0;
14519            whiteTimeRemaining -= lastTickLength;
14520            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14521 //         if(pvInfoList[forwardMostMove-1].time == -1)
14522                  pvInfoList[forwardMostMove-1].time =
14523                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14524         }
14525         flagged = CheckFlags();
14526     }
14527     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14528     CheckTimeControl();
14529
14530     if (flagged || !appData.clockMode) return;
14531
14532     switch (gameMode) {
14533       case MachinePlaysBlack:
14534       case MachinePlaysWhite:
14535       case BeginningOfGame:
14536         if (pausing) return;
14537         break;
14538
14539       case EditGame:
14540       case PlayFromGameFile:
14541       case IcsExamining:
14542         return;
14543
14544       default:
14545         break;
14546     }
14547
14548     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14549         if(WhiteOnMove(forwardMostMove))
14550              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14551         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14552     }
14553
14554     tickStartTM = now;
14555     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14556       whiteTimeRemaining : blackTimeRemaining);
14557     StartClockTimer(intendedTickLength);
14558 }
14559
14560
14561 /* Stop both clocks */
14562 void
14563 StopClocks()
14564 {
14565     long lastTickLength;
14566     TimeMark now;
14567
14568     if (!StopClockTimer()) return;
14569     if (!appData.clockMode) return;
14570
14571     GetTimeMark(&now);
14572
14573     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14574     if (WhiteOnMove(forwardMostMove)) {
14575         if(whiteNPS >= 0) lastTickLength = 0;
14576         whiteTimeRemaining -= lastTickLength;
14577         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14578     } else {
14579         if(blackNPS >= 0) lastTickLength = 0;
14580         blackTimeRemaining -= lastTickLength;
14581         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14582     }
14583     CheckFlags();
14584 }
14585
14586 /* Start clock of player on move.  Time may have been reset, so
14587    if clock is already running, stop and restart it. */
14588 void
14589 StartClocks()
14590 {
14591     (void) StopClockTimer(); /* in case it was running already */
14592     DisplayBothClocks();
14593     if (CheckFlags()) return;
14594
14595     if (!appData.clockMode) return;
14596     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14597
14598     GetTimeMark(&tickStartTM);
14599     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14600       whiteTimeRemaining : blackTimeRemaining);
14601
14602    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14603     whiteNPS = blackNPS = -1;
14604     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14605        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14606         whiteNPS = first.nps;
14607     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14608        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14609         blackNPS = first.nps;
14610     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14611         whiteNPS = second.nps;
14612     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14613         blackNPS = second.nps;
14614     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14615
14616     StartClockTimer(intendedTickLength);
14617 }
14618
14619 char *
14620 TimeString(ms)
14621      long ms;
14622 {
14623     long second, minute, hour, day;
14624     char *sign = "";
14625     static char buf[32];
14626
14627     if (ms > 0 && ms <= 9900) {
14628       /* convert milliseconds to tenths, rounding up */
14629       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14630
14631       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14632       return buf;
14633     }
14634
14635     /* convert milliseconds to seconds, rounding up */
14636     /* use floating point to avoid strangeness of integer division
14637        with negative dividends on many machines */
14638     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14639
14640     if (second < 0) {
14641         sign = "-";
14642         second = -second;
14643     }
14644
14645     day = second / (60 * 60 * 24);
14646     second = second % (60 * 60 * 24);
14647     hour = second / (60 * 60);
14648     second = second % (60 * 60);
14649     minute = second / 60;
14650     second = second % 60;
14651
14652     if (day > 0)
14653       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14654               sign, day, hour, minute, second);
14655     else if (hour > 0)
14656       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14657     else
14658       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14659
14660     return buf;
14661 }
14662
14663
14664 /*
14665  * This is necessary because some C libraries aren't ANSI C compliant yet.
14666  */
14667 char *
14668 StrStr(string, match)
14669      char *string, *match;
14670 {
14671     int i, length;
14672
14673     length = strlen(match);
14674
14675     for (i = strlen(string) - length; i >= 0; i--, string++)
14676       if (!strncmp(match, string, length))
14677         return string;
14678
14679     return NULL;
14680 }
14681
14682 char *
14683 StrCaseStr(string, match)
14684      char *string, *match;
14685 {
14686     int i, j, length;
14687
14688     length = strlen(match);
14689
14690     for (i = strlen(string) - length; i >= 0; i--, string++) {
14691         for (j = 0; j < length; j++) {
14692             if (ToLower(match[j]) != ToLower(string[j]))
14693               break;
14694         }
14695         if (j == length) return string;
14696     }
14697
14698     return NULL;
14699 }
14700
14701 #ifndef _amigados
14702 int
14703 StrCaseCmp(s1, s2)
14704      char *s1, *s2;
14705 {
14706     char c1, c2;
14707
14708     for (;;) {
14709         c1 = ToLower(*s1++);
14710         c2 = ToLower(*s2++);
14711         if (c1 > c2) return 1;
14712         if (c1 < c2) return -1;
14713         if (c1 == NULLCHAR) return 0;
14714     }
14715 }
14716
14717
14718 int
14719 ToLower(c)
14720      int c;
14721 {
14722     return isupper(c) ? tolower(c) : c;
14723 }
14724
14725
14726 int
14727 ToUpper(c)
14728      int c;
14729 {
14730     return islower(c) ? toupper(c) : c;
14731 }
14732 #endif /* !_amigados    */
14733
14734 char *
14735 StrSave(s)
14736      char *s;
14737 {
14738   char *ret;
14739
14740   if ((ret = (char *) malloc(strlen(s) + 1)))
14741     {
14742       safeStrCpy(ret, s, strlen(s)+1);
14743     }
14744   return ret;
14745 }
14746
14747 char *
14748 StrSavePtr(s, savePtr)
14749      char *s, **savePtr;
14750 {
14751     if (*savePtr) {
14752         free(*savePtr);
14753     }
14754     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14755       safeStrCpy(*savePtr, s, strlen(s)+1);
14756     }
14757     return(*savePtr);
14758 }
14759
14760 char *
14761 PGNDate()
14762 {
14763     time_t clock;
14764     struct tm *tm;
14765     char buf[MSG_SIZ];
14766
14767     clock = time((time_t *)NULL);
14768     tm = localtime(&clock);
14769     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14770             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14771     return StrSave(buf);
14772 }
14773
14774
14775 char *
14776 PositionToFEN(move, overrideCastling)
14777      int move;
14778      char *overrideCastling;
14779 {
14780     int i, j, fromX, fromY, toX, toY;
14781     int whiteToPlay;
14782     char buf[128];
14783     char *p, *q;
14784     int emptycount;
14785     ChessSquare piece;
14786
14787     whiteToPlay = (gameMode == EditPosition) ?
14788       !blackPlaysFirst : (move % 2 == 0);
14789     p = buf;
14790
14791     /* Piece placement data */
14792     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14793         emptycount = 0;
14794         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14795             if (boards[move][i][j] == EmptySquare) {
14796                 emptycount++;
14797             } else { ChessSquare piece = boards[move][i][j];
14798                 if (emptycount > 0) {
14799                     if(emptycount<10) /* [HGM] can be >= 10 */
14800                         *p++ = '0' + emptycount;
14801                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14802                     emptycount = 0;
14803                 }
14804                 if(PieceToChar(piece) == '+') {
14805                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14806                     *p++ = '+';
14807                     piece = (ChessSquare)(DEMOTED piece);
14808                 }
14809                 *p++ = PieceToChar(piece);
14810                 if(p[-1] == '~') {
14811                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14812                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14813                     *p++ = '~';
14814                 }
14815             }
14816         }
14817         if (emptycount > 0) {
14818             if(emptycount<10) /* [HGM] can be >= 10 */
14819                 *p++ = '0' + emptycount;
14820             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14821             emptycount = 0;
14822         }
14823         *p++ = '/';
14824     }
14825     *(p - 1) = ' ';
14826
14827     /* [HGM] print Crazyhouse or Shogi holdings */
14828     if( gameInfo.holdingsWidth ) {
14829         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14830         q = p;
14831         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14832             piece = boards[move][i][BOARD_WIDTH-1];
14833             if( piece != EmptySquare )
14834               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14835                   *p++ = PieceToChar(piece);
14836         }
14837         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14838             piece = boards[move][BOARD_HEIGHT-i-1][0];
14839             if( piece != EmptySquare )
14840               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14841                   *p++ = PieceToChar(piece);
14842         }
14843
14844         if( q == p ) *p++ = '-';
14845         *p++ = ']';
14846         *p++ = ' ';
14847     }
14848
14849     /* Active color */
14850     *p++ = whiteToPlay ? 'w' : 'b';
14851     *p++ = ' ';
14852
14853   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14854     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14855   } else {
14856   if(nrCastlingRights) {
14857      q = p;
14858      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14859        /* [HGM] write directly from rights */
14860            if(boards[move][CASTLING][2] != NoRights &&
14861               boards[move][CASTLING][0] != NoRights   )
14862                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14863            if(boards[move][CASTLING][2] != NoRights &&
14864               boards[move][CASTLING][1] != NoRights   )
14865                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14866            if(boards[move][CASTLING][5] != NoRights &&
14867               boards[move][CASTLING][3] != NoRights   )
14868                 *p++ = boards[move][CASTLING][3] + AAA;
14869            if(boards[move][CASTLING][5] != NoRights &&
14870               boards[move][CASTLING][4] != NoRights   )
14871                 *p++ = boards[move][CASTLING][4] + AAA;
14872      } else {
14873
14874         /* [HGM] write true castling rights */
14875         if( nrCastlingRights == 6 ) {
14876             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14877                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14878             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14879                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14880             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14881                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14882             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14883                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14884         }
14885      }
14886      if (q == p) *p++ = '-'; /* No castling rights */
14887      *p++ = ' ';
14888   }
14889
14890   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14891      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14892     /* En passant target square */
14893     if (move > backwardMostMove) {
14894         fromX = moveList[move - 1][0] - AAA;
14895         fromY = moveList[move - 1][1] - ONE;
14896         toX = moveList[move - 1][2] - AAA;
14897         toY = moveList[move - 1][3] - ONE;
14898         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14899             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14900             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14901             fromX == toX) {
14902             /* 2-square pawn move just happened */
14903             *p++ = toX + AAA;
14904             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14905         } else {
14906             *p++ = '-';
14907         }
14908     } else if(move == backwardMostMove) {
14909         // [HGM] perhaps we should always do it like this, and forget the above?
14910         if((signed char)boards[move][EP_STATUS] >= 0) {
14911             *p++ = boards[move][EP_STATUS] + AAA;
14912             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14913         } else {
14914             *p++ = '-';
14915         }
14916     } else {
14917         *p++ = '-';
14918     }
14919     *p++ = ' ';
14920   }
14921   }
14922
14923     /* [HGM] find reversible plies */
14924     {   int i = 0, j=move;
14925
14926         if (appData.debugMode) { int k;
14927             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14928             for(k=backwardMostMove; k<=forwardMostMove; k++)
14929                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14930
14931         }
14932
14933         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14934         if( j == backwardMostMove ) i += initialRulePlies;
14935         sprintf(p, "%d ", i);
14936         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14937     }
14938     /* Fullmove number */
14939     sprintf(p, "%d", (move / 2) + 1);
14940
14941     return StrSave(buf);
14942 }
14943
14944 Boolean
14945 ParseFEN(board, blackPlaysFirst, fen)
14946     Board board;
14947      int *blackPlaysFirst;
14948      char *fen;
14949 {
14950     int i, j;
14951     char *p, c;
14952     int emptycount;
14953     ChessSquare piece;
14954
14955     p = fen;
14956
14957     /* [HGM] by default clear Crazyhouse holdings, if present */
14958     if(gameInfo.holdingsWidth) {
14959        for(i=0; i<BOARD_HEIGHT; i++) {
14960            board[i][0]             = EmptySquare; /* black holdings */
14961            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14962            board[i][1]             = (ChessSquare) 0; /* black counts */
14963            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14964        }
14965     }
14966
14967     /* Piece placement data */
14968     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14969         j = 0;
14970         for (;;) {
14971             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14972                 if (*p == '/') p++;
14973                 emptycount = gameInfo.boardWidth - j;
14974                 while (emptycount--)
14975                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14976                 break;
14977 #if(BOARD_FILES >= 10)
14978             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14979                 p++; emptycount=10;
14980                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14981                 while (emptycount--)
14982                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14983 #endif
14984             } else if (isdigit(*p)) {
14985                 emptycount = *p++ - '0';
14986                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14987                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14988                 while (emptycount--)
14989                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14990             } else if (*p == '+' || isalpha(*p)) {
14991                 if (j >= gameInfo.boardWidth) return FALSE;
14992                 if(*p=='+') {
14993                     piece = CharToPiece(*++p);
14994                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14995                     piece = (ChessSquare) (PROMOTED piece ); p++;
14996                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14997                 } else piece = CharToPiece(*p++);
14998
14999                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15000                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15001                     piece = (ChessSquare) (PROMOTED piece);
15002                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15003                     p++;
15004                 }
15005                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15006             } else {
15007                 return FALSE;
15008             }
15009         }
15010     }
15011     while (*p == '/' || *p == ' ') p++;
15012
15013     /* [HGM] look for Crazyhouse holdings here */
15014     while(*p==' ') p++;
15015     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15016         if(*p == '[') p++;
15017         if(*p == '-' ) p++; /* empty holdings */ else {
15018             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15019             /* if we would allow FEN reading to set board size, we would   */
15020             /* have to add holdings and shift the board read so far here   */
15021             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15022                 p++;
15023                 if((int) piece >= (int) BlackPawn ) {
15024                     i = (int)piece - (int)BlackPawn;
15025                     i = PieceToNumber((ChessSquare)i);
15026                     if( i >= gameInfo.holdingsSize ) return FALSE;
15027                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15028                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15029                 } else {
15030                     i = (int)piece - (int)WhitePawn;
15031                     i = PieceToNumber((ChessSquare)i);
15032                     if( i >= gameInfo.holdingsSize ) return FALSE;
15033                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15034                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15035                 }
15036             }
15037         }
15038         if(*p == ']') p++;
15039     }
15040
15041     while(*p == ' ') p++;
15042
15043     /* Active color */
15044     c = *p++;
15045     if(appData.colorNickNames) {
15046       if( c == appData.colorNickNames[0] ) c = 'w'; else
15047       if( c == appData.colorNickNames[1] ) c = 'b';
15048     }
15049     switch (c) {
15050       case 'w':
15051         *blackPlaysFirst = FALSE;
15052         break;
15053       case 'b':
15054         *blackPlaysFirst = TRUE;
15055         break;
15056       default:
15057         return FALSE;
15058     }
15059
15060     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15061     /* return the extra info in global variiables             */
15062
15063     /* set defaults in case FEN is incomplete */
15064     board[EP_STATUS] = EP_UNKNOWN;
15065     for(i=0; i<nrCastlingRights; i++ ) {
15066         board[CASTLING][i] =
15067             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15068     }   /* assume possible unless obviously impossible */
15069     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15070     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15071     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15072                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15073     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15074     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15075     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15076                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15077     FENrulePlies = 0;
15078
15079     while(*p==' ') p++;
15080     if(nrCastlingRights) {
15081       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15082           /* castling indicator present, so default becomes no castlings */
15083           for(i=0; i<nrCastlingRights; i++ ) {
15084                  board[CASTLING][i] = NoRights;
15085           }
15086       }
15087       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15088              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15089              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15090              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15091         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15092
15093         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15094             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15095             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15096         }
15097         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15098             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15099         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15100                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15101         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15102                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15103         switch(c) {
15104           case'K':
15105               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15106               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15107               board[CASTLING][2] = whiteKingFile;
15108               break;
15109           case'Q':
15110               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15111               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15112               board[CASTLING][2] = whiteKingFile;
15113               break;
15114           case'k':
15115               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15116               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15117               board[CASTLING][5] = blackKingFile;
15118               break;
15119           case'q':
15120               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15121               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15122               board[CASTLING][5] = blackKingFile;
15123           case '-':
15124               break;
15125           default: /* FRC castlings */
15126               if(c >= 'a') { /* black rights */
15127                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15128                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15129                   if(i == BOARD_RGHT) break;
15130                   board[CASTLING][5] = i;
15131                   c -= AAA;
15132                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15133                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15134                   if(c > i)
15135                       board[CASTLING][3] = c;
15136                   else
15137                       board[CASTLING][4] = c;
15138               } else { /* white rights */
15139                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15140                     if(board[0][i] == WhiteKing) break;
15141                   if(i == BOARD_RGHT) break;
15142                   board[CASTLING][2] = i;
15143                   c -= AAA - 'a' + 'A';
15144                   if(board[0][c] >= WhiteKing) break;
15145                   if(c > i)
15146                       board[CASTLING][0] = c;
15147                   else
15148                       board[CASTLING][1] = c;
15149               }
15150         }
15151       }
15152       for(i=0; i<nrCastlingRights; i++)
15153         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15154     if (appData.debugMode) {
15155         fprintf(debugFP, "FEN castling rights:");
15156         for(i=0; i<nrCastlingRights; i++)
15157         fprintf(debugFP, " %d", board[CASTLING][i]);
15158         fprintf(debugFP, "\n");
15159     }
15160
15161       while(*p==' ') p++;
15162     }
15163
15164     /* read e.p. field in games that know e.p. capture */
15165     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15166        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15167       if(*p=='-') {
15168         p++; board[EP_STATUS] = EP_NONE;
15169       } else {
15170          char c = *p++ - AAA;
15171
15172          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15173          if(*p >= '0' && *p <='9') p++;
15174          board[EP_STATUS] = c;
15175       }
15176     }
15177
15178
15179     if(sscanf(p, "%d", &i) == 1) {
15180         FENrulePlies = i; /* 50-move ply counter */
15181         /* (The move number is still ignored)    */
15182     }
15183
15184     return TRUE;
15185 }
15186
15187 void
15188 EditPositionPasteFEN(char *fen)
15189 {
15190   if (fen != NULL) {
15191     Board initial_position;
15192
15193     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15194       DisplayError(_("Bad FEN position in clipboard"), 0);
15195       return ;
15196     } else {
15197       int savedBlackPlaysFirst = blackPlaysFirst;
15198       EditPositionEvent();
15199       blackPlaysFirst = savedBlackPlaysFirst;
15200       CopyBoard(boards[0], initial_position);
15201       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15202       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15203       DisplayBothClocks();
15204       DrawPosition(FALSE, boards[currentMove]);
15205     }
15206   }
15207 }
15208
15209 static char cseq[12] = "\\   ";
15210
15211 Boolean set_cont_sequence(char *new_seq)
15212 {
15213     int len;
15214     Boolean ret;
15215
15216     // handle bad attempts to set the sequence
15217         if (!new_seq)
15218                 return 0; // acceptable error - no debug
15219
15220     len = strlen(new_seq);
15221     ret = (len > 0) && (len < sizeof(cseq));
15222     if (ret)
15223       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15224     else if (appData.debugMode)
15225       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15226     return ret;
15227 }
15228
15229 /*
15230     reformat a source message so words don't cross the width boundary.  internal
15231     newlines are not removed.  returns the wrapped size (no null character unless
15232     included in source message).  If dest is NULL, only calculate the size required
15233     for the dest buffer.  lp argument indicats line position upon entry, and it's
15234     passed back upon exit.
15235 */
15236 int wrap(char *dest, char *src, int count, int width, int *lp)
15237 {
15238     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15239
15240     cseq_len = strlen(cseq);
15241     old_line = line = *lp;
15242     ansi = len = clen = 0;
15243
15244     for (i=0; i < count; i++)
15245     {
15246         if (src[i] == '\033')
15247             ansi = 1;
15248
15249         // if we hit the width, back up
15250         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15251         {
15252             // store i & len in case the word is too long
15253             old_i = i, old_len = len;
15254
15255             // find the end of the last word
15256             while (i && src[i] != ' ' && src[i] != '\n')
15257             {
15258                 i--;
15259                 len--;
15260             }
15261
15262             // word too long?  restore i & len before splitting it
15263             if ((old_i-i+clen) >= width)
15264             {
15265                 i = old_i;
15266                 len = old_len;
15267             }
15268
15269             // extra space?
15270             if (i && src[i-1] == ' ')
15271                 len--;
15272
15273             if (src[i] != ' ' && src[i] != '\n')
15274             {
15275                 i--;
15276                 if (len)
15277                     len--;
15278             }
15279
15280             // now append the newline and continuation sequence
15281             if (dest)
15282                 dest[len] = '\n';
15283             len++;
15284             if (dest)
15285                 strncpy(dest+len, cseq, cseq_len);
15286             len += cseq_len;
15287             line = cseq_len;
15288             clen = cseq_len;
15289             continue;
15290         }
15291
15292         if (dest)
15293             dest[len] = src[i];
15294         len++;
15295         if (!ansi)
15296             line++;
15297         if (src[i] == '\n')
15298             line = 0;
15299         if (src[i] == 'm')
15300             ansi = 0;
15301     }
15302     if (dest && appData.debugMode)
15303     {
15304         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15305             count, width, line, len, *lp);
15306         show_bytes(debugFP, src, count);
15307         fprintf(debugFP, "\ndest: ");
15308         show_bytes(debugFP, dest, len);
15309         fprintf(debugFP, "\n");
15310     }
15311     *lp = dest ? line : old_line;
15312
15313     return len;
15314 }
15315
15316 // [HGM] vari: routines for shelving variations
15317
15318 void
15319 PushTail(int firstMove, int lastMove)
15320 {
15321         int i, j, nrMoves = lastMove - firstMove;
15322
15323         if(appData.icsActive) { // only in local mode
15324                 forwardMostMove = currentMove; // mimic old ICS behavior
15325                 return;
15326         }
15327         if(storedGames >= MAX_VARIATIONS-1) return;
15328
15329         // push current tail of game on stack
15330         savedResult[storedGames] = gameInfo.result;
15331         savedDetails[storedGames] = gameInfo.resultDetails;
15332         gameInfo.resultDetails = NULL;
15333         savedFirst[storedGames] = firstMove;
15334         savedLast [storedGames] = lastMove;
15335         savedFramePtr[storedGames] = framePtr;
15336         framePtr -= nrMoves; // reserve space for the boards
15337         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15338             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15339             for(j=0; j<MOVE_LEN; j++)
15340                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15341             for(j=0; j<2*MOVE_LEN; j++)
15342                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15343             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15344             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15345             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15346             pvInfoList[firstMove+i-1].depth = 0;
15347             commentList[framePtr+i] = commentList[firstMove+i];
15348             commentList[firstMove+i] = NULL;
15349         }
15350
15351         storedGames++;
15352         forwardMostMove = firstMove; // truncate game so we can start variation
15353         if(storedGames == 1) GreyRevert(FALSE);
15354 }
15355
15356 Boolean
15357 PopTail(Boolean annotate)
15358 {
15359         int i, j, nrMoves;
15360         char buf[8000], moveBuf[20];
15361
15362         if(appData.icsActive) return FALSE; // only in local mode
15363         if(!storedGames) return FALSE; // sanity
15364         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15365
15366         storedGames--;
15367         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15368         nrMoves = savedLast[storedGames] - currentMove;
15369         if(annotate) {
15370                 int cnt = 10;
15371                 if(!WhiteOnMove(currentMove))
15372                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15373                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15374                 for(i=currentMove; i<forwardMostMove; i++) {
15375                         if(WhiteOnMove(i))
15376                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15377                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15378                         strcat(buf, moveBuf);
15379                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15380                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15381                 }
15382                 strcat(buf, ")");
15383         }
15384         for(i=1; i<=nrMoves; i++) { // copy last variation back
15385             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15386             for(j=0; j<MOVE_LEN; j++)
15387                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15388             for(j=0; j<2*MOVE_LEN; j++)
15389                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15390             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15391             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15392             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15393             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15394             commentList[currentMove+i] = commentList[framePtr+i];
15395             commentList[framePtr+i] = NULL;
15396         }
15397         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15398         framePtr = savedFramePtr[storedGames];
15399         gameInfo.result = savedResult[storedGames];
15400         if(gameInfo.resultDetails != NULL) {
15401             free(gameInfo.resultDetails);
15402       }
15403         gameInfo.resultDetails = savedDetails[storedGames];
15404         forwardMostMove = currentMove + nrMoves;
15405         if(storedGames == 0) GreyRevert(TRUE);
15406         return TRUE;
15407 }
15408
15409 void
15410 CleanupTail()
15411 {       // remove all shelved variations
15412         int i;
15413         for(i=0; i<storedGames; i++) {
15414             if(savedDetails[i])
15415                 free(savedDetails[i]);
15416             savedDetails[i] = NULL;
15417         }
15418         for(i=framePtr; i<MAX_MOVES; i++) {
15419                 if(commentList[i]) free(commentList[i]);
15420                 commentList[i] = NULL;
15421         }
15422         framePtr = MAX_MOVES-1;
15423         storedGames = 0;
15424 }
15425
15426 void
15427 LoadVariation(int index, char *text)
15428 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15429         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15430         int level = 0, move;
15431
15432         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15433         // first find outermost bracketing variation
15434         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15435             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15436                 if(*p == '{') wait = '}'; else
15437                 if(*p == '[') wait = ']'; else
15438                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15439                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15440             }
15441             if(*p == wait) wait = NULLCHAR; // closing ]} found
15442             p++;
15443         }
15444         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15445         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15446         end[1] = NULLCHAR; // clip off comment beyond variation
15447         ToNrEvent(currentMove-1);
15448         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15449         // kludge: use ParsePV() to append variation to game
15450         move = currentMove;
15451         ParsePV(start, TRUE);
15452         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15453         ClearPremoveHighlights();
15454         CommentPopDown();
15455         ToNrEvent(currentMove+1);
15456 }
15457