bc35e345fb248bda07ea73482b6c73ed4812291d
[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                 sending_ICS_password = 0; // in case we come back to login
3120                 sending_ICS_login = 1; 
3121                 continue;
3122             }
3123             /* need to shadow the password */
3124             if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3125               sending_ICS_password = 1;
3126               continue;
3127             }
3128               
3129             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ && 
3130                 (looking_at(buf, &i, "\n<12> ") ||
3131                  looking_at(buf, &i, "<12> "))) {
3132                 loggedOn = TRUE;
3133                 if (oldi > next_out) {
3134                     SendToPlayer(&buf[next_out], oldi - next_out);
3135                 }
3136                 next_out = i;
3137                 started = STARTED_BOARD;
3138                 parse_pos = 0;
3139                 continue;
3140             }
3141
3142             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3143                 looking_at(buf, &i, "<b1> ")) {
3144                 if (oldi > next_out) {
3145                     SendToPlayer(&buf[next_out], oldi - next_out);
3146                 }
3147                 next_out = i;
3148                 started = STARTED_HOLDINGS;
3149                 parse_pos = 0;
3150                 continue;
3151             }
3152
3153             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3154                 loggedOn = TRUE;
3155                 /* Header for a move list -- first line */
3156
3157                 switch (ics_getting_history) {
3158                   case H_FALSE:
3159                     switch (gameMode) {
3160                       case IcsIdle:
3161                       case BeginningOfGame:
3162                         /* User typed "moves" or "oldmoves" while we
3163                            were idle.  Pretend we asked for these
3164                            moves and soak them up so user can step
3165                            through them and/or save them.
3166                            */
3167                         Reset(FALSE, TRUE);
3168                         gameMode = IcsObserving;
3169                         ModeHighlight();
3170                         ics_gamenum = -1;
3171                         ics_getting_history = H_GOT_UNREQ_HEADER;
3172                         break;
3173                       case EditGame: /*?*/
3174                       case EditPosition: /*?*/
3175                         /* Should above feature work in these modes too? */
3176                         /* For now it doesn't */
3177                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3178                         break;
3179                       default:
3180                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3181                         break;
3182                     }
3183                     break;
3184                   case H_REQUESTED:
3185                     /* Is this the right one? */
3186                     if (gameInfo.white && gameInfo.black &&
3187                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3188                         strcmp(gameInfo.black, star_match[2]) == 0) {
3189                         /* All is well */
3190                         ics_getting_history = H_GOT_REQ_HEADER;
3191                     }
3192                     break;
3193                   case H_GOT_REQ_HEADER:
3194                   case H_GOT_UNREQ_HEADER:
3195                   case H_GOT_UNWANTED_HEADER:
3196                   case H_GETTING_MOVES:
3197                     /* Should not happen */
3198                     DisplayError(_("Error gathering move list: two headers"), 0);
3199                     ics_getting_history = H_FALSE;
3200                     break;
3201                 }
3202
3203                 /* Save player ratings into gameInfo if needed */
3204                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3205                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3206                     (gameInfo.whiteRating == -1 ||
3207                      gameInfo.blackRating == -1)) {
3208
3209                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3210                     gameInfo.blackRating = string_to_rating(star_match[3]);
3211                     if (appData.debugMode)
3212                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3213                               gameInfo.whiteRating, gameInfo.blackRating);
3214                 }
3215                 continue;
3216             }
3217
3218             if (looking_at(buf, &i,
3219               "* * match, initial time: * minute*, increment: * second")) {
3220                 /* Header for a move list -- second line */
3221                 /* Initial board will follow if this is a wild game */
3222                 if (gameInfo.event != NULL) free(gameInfo.event);
3223                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3224                 gameInfo.event = StrSave(str);
3225                 /* [HGM] we switched variant. Translate boards if needed. */
3226                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3227                 continue;
3228             }
3229
3230             if (looking_at(buf, &i, "Move  ")) {
3231                 /* Beginning of a move list */
3232                 switch (ics_getting_history) {
3233                   case H_FALSE:
3234                     /* Normally should not happen */
3235                     /* Maybe user hit reset while we were parsing */
3236                     break;
3237                   case H_REQUESTED:
3238                     /* Happens if we are ignoring a move list that is not
3239                      * the one we just requested.  Common if the user
3240                      * tries to observe two games without turning off
3241                      * getMoveList */
3242                     break;
3243                   case H_GETTING_MOVES:
3244                     /* Should not happen */
3245                     DisplayError(_("Error gathering move list: nested"), 0);
3246                     ics_getting_history = H_FALSE;
3247                     break;
3248                   case H_GOT_REQ_HEADER:
3249                     ics_getting_history = H_GETTING_MOVES;
3250                     started = STARTED_MOVES;
3251                     parse_pos = 0;
3252                     if (oldi > next_out) {
3253                         SendToPlayer(&buf[next_out], oldi - next_out);
3254                     }
3255                     break;
3256                   case H_GOT_UNREQ_HEADER:
3257                     ics_getting_history = H_GETTING_MOVES;
3258                     started = STARTED_MOVES_NOHIDE;
3259                     parse_pos = 0;
3260                     break;
3261                   case H_GOT_UNWANTED_HEADER:
3262                     ics_getting_history = H_FALSE;
3263                     break;
3264                 }
3265                 continue;
3266             }
3267
3268             if (looking_at(buf, &i, "% ") ||
3269                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3270                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3271                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3272                     soughtPending = FALSE;
3273                     seekGraphUp = TRUE;
3274                     DrawSeekGraph();
3275                 }
3276                 if(suppressKibitz) next_out = i;
3277                 savingComment = FALSE;
3278                 suppressKibitz = 0;
3279                 switch (started) {
3280                   case STARTED_MOVES:
3281                   case STARTED_MOVES_NOHIDE:
3282                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3283                     parse[parse_pos + i - oldi] = NULLCHAR;
3284                     ParseGameHistory(parse);
3285 #if ZIPPY
3286                     if (appData.zippyPlay && first.initDone) {
3287                         FeedMovesToProgram(&first, forwardMostMove);
3288                         if (gameMode == IcsPlayingWhite) {
3289                             if (WhiteOnMove(forwardMostMove)) {
3290                                 if (first.sendTime) {
3291                                   if (first.useColors) {
3292                                     SendToProgram("black\n", &first);
3293                                   }
3294                                   SendTimeRemaining(&first, TRUE);
3295                                 }
3296                                 if (first.useColors) {
3297                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3298                                 }
3299                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3300                                 first.maybeThinking = TRUE;
3301                             } else {
3302                                 if (first.usePlayother) {
3303                                   if (first.sendTime) {
3304                                     SendTimeRemaining(&first, TRUE);
3305                                   }
3306                                   SendToProgram("playother\n", &first);
3307                                   firstMove = FALSE;
3308                                 } else {
3309                                   firstMove = TRUE;
3310                                 }
3311                             }
3312                         } else if (gameMode == IcsPlayingBlack) {
3313                             if (!WhiteOnMove(forwardMostMove)) {
3314                                 if (first.sendTime) {
3315                                   if (first.useColors) {
3316                                     SendToProgram("white\n", &first);
3317                                   }
3318                                   SendTimeRemaining(&first, FALSE);
3319                                 }
3320                                 if (first.useColors) {
3321                                   SendToProgram("black\n", &first);
3322                                 }
3323                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3324                                 first.maybeThinking = TRUE;
3325                             } else {
3326                                 if (first.usePlayother) {
3327                                   if (first.sendTime) {
3328                                     SendTimeRemaining(&first, FALSE);
3329                                   }
3330                                   SendToProgram("playother\n", &first);
3331                                   firstMove = FALSE;
3332                                 } else {
3333                                   firstMove = TRUE;
3334                                 }
3335                             }
3336                         }
3337                     }
3338 #endif
3339                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3340                         /* Moves came from oldmoves or moves command
3341                            while we weren't doing anything else.
3342                            */
3343                         currentMove = forwardMostMove;
3344                         ClearHighlights();/*!!could figure this out*/
3345                         flipView = appData.flipView;
3346                         DrawPosition(TRUE, boards[currentMove]);
3347                         DisplayBothClocks();
3348                         snprintf(str, MSG_SIZ, "%s vs. %s",
3349                                 gameInfo.white, gameInfo.black);
3350                         DisplayTitle(str);
3351                         gameMode = IcsIdle;
3352                     } else {
3353                         /* Moves were history of an active game */
3354                         if (gameInfo.resultDetails != NULL) {
3355                             free(gameInfo.resultDetails);
3356                             gameInfo.resultDetails = NULL;
3357                         }
3358                     }
3359                     HistorySet(parseList, backwardMostMove,
3360                                forwardMostMove, currentMove-1);
3361                     DisplayMove(currentMove - 1);
3362                     if (started == STARTED_MOVES) next_out = i;
3363                     started = STARTED_NONE;
3364                     ics_getting_history = H_FALSE;
3365                     break;
3366
3367                   case STARTED_OBSERVE:
3368                     started = STARTED_NONE;
3369                     SendToICS(ics_prefix);
3370                     SendToICS("refresh\n");
3371                     break;
3372
3373                   default:
3374                     break;
3375                 }
3376                 if(bookHit) { // [HGM] book: simulate book reply
3377                     static char bookMove[MSG_SIZ]; // a bit generous?
3378
3379                     programStats.nodes = programStats.depth = programStats.time =
3380                     programStats.score = programStats.got_only_move = 0;
3381                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3382
3383                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3384                     strcat(bookMove, bookHit);
3385                     HandleMachineMove(bookMove, &first);
3386                 }
3387                 continue;
3388             }
3389
3390             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3391                  started == STARTED_HOLDINGS ||
3392                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3393                 /* Accumulate characters in move list or board */
3394                 parse[parse_pos++] = buf[i];
3395             }
3396
3397             /* Start of game messages.  Mostly we detect start of game
3398                when the first board image arrives.  On some versions
3399                of the ICS, though, we need to do a "refresh" after starting
3400                to observe in order to get the current board right away. */
3401             if (looking_at(buf, &i, "Adding game * to observation list")) {
3402                 started = STARTED_OBSERVE;
3403                 continue;
3404             }
3405
3406             /* Handle auto-observe */
3407             if (appData.autoObserve &&
3408                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3409                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3410                 char *player;
3411                 /* Choose the player that was highlighted, if any. */
3412                 if (star_match[0][0] == '\033' ||
3413                     star_match[1][0] != '\033') {
3414                     player = star_match[0];
3415                 } else {
3416                     player = star_match[2];
3417                 }
3418                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3419                         ics_prefix, StripHighlightAndTitle(player));
3420                 SendToICS(str);
3421
3422                 /* Save ratings from notify string */
3423                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3424                 player1Rating = string_to_rating(star_match[1]);
3425                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3426                 player2Rating = string_to_rating(star_match[3]);
3427
3428                 if (appData.debugMode)
3429                   fprintf(debugFP,
3430                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3431                           player1Name, player1Rating,
3432                           player2Name, player2Rating);
3433
3434                 continue;
3435             }
3436
3437             /* Deal with automatic examine mode after a game,
3438                and with IcsObserving -> IcsExamining transition */
3439             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3440                 looking_at(buf, &i, "has made you an examiner of game *")) {
3441
3442                 int gamenum = atoi(star_match[0]);
3443                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3444                     gamenum == ics_gamenum) {
3445                     /* We were already playing or observing this game;
3446                        no need to refetch history */
3447                     gameMode = IcsExamining;
3448                     if (pausing) {
3449                         pauseExamForwardMostMove = forwardMostMove;
3450                     } else if (currentMove < forwardMostMove) {
3451                         ForwardInner(forwardMostMove);
3452                     }
3453                 } else {
3454                     /* I don't think this case really can happen */
3455                     SendToICS(ics_prefix);
3456                     SendToICS("refresh\n");
3457                 }
3458                 continue;
3459             }
3460
3461             /* Error messages */
3462 //          if (ics_user_moved) {
3463             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3464                 if (looking_at(buf, &i, "Illegal move") ||
3465                     looking_at(buf, &i, "Not a legal move") ||
3466                     looking_at(buf, &i, "Your king is in check") ||
3467                     looking_at(buf, &i, "It isn't your turn") ||
3468                     looking_at(buf, &i, "It is not your move")) {
3469                     /* Illegal move */
3470                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3471                         currentMove = forwardMostMove-1;
3472                         DisplayMove(currentMove - 1); /* before DMError */
3473                         DrawPosition(FALSE, boards[currentMove]);
3474                         SwitchClocks(forwardMostMove-1); // [HGM] race
3475                         DisplayBothClocks();
3476                     }
3477                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3478                     ics_user_moved = 0;
3479                     continue;
3480                 }
3481             }
3482
3483             if (looking_at(buf, &i, "still have time") ||
3484                 looking_at(buf, &i, "not out of time") ||
3485                 looking_at(buf, &i, "either player is out of time") ||
3486                 looking_at(buf, &i, "has timeseal; checking")) {
3487                 /* We must have called his flag a little too soon */
3488                 whiteFlag = blackFlag = FALSE;
3489                 continue;
3490             }
3491
3492             if (looking_at(buf, &i, "added * seconds to") ||
3493                 looking_at(buf, &i, "seconds were added to")) {
3494                 /* Update the clocks */
3495                 SendToICS(ics_prefix);
3496                 SendToICS("refresh\n");
3497                 continue;
3498             }
3499
3500             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3501                 ics_clock_paused = TRUE;
3502                 StopClocks();
3503                 continue;
3504             }
3505
3506             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3507                 ics_clock_paused = FALSE;
3508                 StartClocks();
3509                 continue;
3510             }
3511
3512             /* Grab player ratings from the Creating: message.
3513                Note we have to check for the special case when
3514                the ICS inserts things like [white] or [black]. */
3515             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3516                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3517                 /* star_matches:
3518                    0    player 1 name (not necessarily white)
3519                    1    player 1 rating
3520                    2    empty, white, or black (IGNORED)
3521                    3    player 2 name (not necessarily black)
3522                    4    player 2 rating
3523
3524                    The names/ratings are sorted out when the game
3525                    actually starts (below).
3526                 */
3527                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3528                 player1Rating = string_to_rating(star_match[1]);
3529                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3530                 player2Rating = string_to_rating(star_match[4]);
3531
3532                 if (appData.debugMode)
3533                   fprintf(debugFP,
3534                           "Ratings from 'Creating:' %s %d, %s %d\n",
3535                           player1Name, player1Rating,
3536                           player2Name, player2Rating);
3537
3538                 continue;
3539             }
3540
3541             /* Improved generic start/end-of-game messages */
3542             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3543                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3544                 /* If tkind == 0: */
3545                 /* star_match[0] is the game number */
3546                 /*           [1] is the white player's name */
3547                 /*           [2] is the black player's name */
3548                 /* For end-of-game: */
3549                 /*           [3] is the reason for the game end */
3550                 /*           [4] is a PGN end game-token, preceded by " " */
3551                 /* For start-of-game: */
3552                 /*           [3] begins with "Creating" or "Continuing" */
3553                 /*           [4] is " *" or empty (don't care). */
3554                 int gamenum = atoi(star_match[0]);
3555                 char *whitename, *blackname, *why, *endtoken;
3556                 ChessMove endtype = EndOfFile;
3557
3558                 if (tkind == 0) {
3559                   whitename = star_match[1];
3560                   blackname = star_match[2];
3561                   why = star_match[3];
3562                   endtoken = star_match[4];
3563                 } else {
3564                   whitename = star_match[1];
3565                   blackname = star_match[3];
3566                   why = star_match[5];
3567                   endtoken = star_match[6];
3568                 }
3569
3570                 /* Game start messages */
3571                 if (strncmp(why, "Creating ", 9) == 0 ||
3572                     strncmp(why, "Continuing ", 11) == 0) {
3573                     gs_gamenum = gamenum;
3574                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3575                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3576 #if ZIPPY
3577                     if (appData.zippyPlay) {
3578                         ZippyGameStart(whitename, blackname);
3579                     }
3580 #endif /*ZIPPY*/
3581                     partnerBoardValid = FALSE; // [HGM] bughouse
3582                     continue;
3583                 }
3584
3585                 /* Game end messages */
3586                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3587                     ics_gamenum != gamenum) {
3588                     continue;
3589                 }
3590                 while (endtoken[0] == ' ') endtoken++;
3591                 switch (endtoken[0]) {
3592                   case '*':
3593                   default:
3594                     endtype = GameUnfinished;
3595                     break;
3596                   case '0':
3597                     endtype = BlackWins;
3598                     break;
3599                   case '1':
3600                     if (endtoken[1] == '/')
3601                       endtype = GameIsDrawn;
3602                     else
3603                       endtype = WhiteWins;
3604                     break;
3605                 }
3606                 GameEnds(endtype, why, GE_ICS);
3607 #if ZIPPY
3608                 if (appData.zippyPlay && first.initDone) {
3609                     ZippyGameEnd(endtype, why);
3610                     if (first.pr == NULL) {
3611                       /* Start the next process early so that we'll
3612                          be ready for the next challenge */
3613                       StartChessProgram(&first);
3614                     }
3615                     /* Send "new" early, in case this command takes
3616                        a long time to finish, so that we'll be ready
3617                        for the next challenge. */
3618                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3619                     Reset(TRUE, TRUE);
3620                 }
3621 #endif /*ZIPPY*/
3622                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3623                 continue;
3624             }
3625
3626             if (looking_at(buf, &i, "Removing game * from observation") ||
3627                 looking_at(buf, &i, "no longer observing game *") ||
3628                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3629                 if (gameMode == IcsObserving &&
3630                     atoi(star_match[0]) == ics_gamenum)
3631                   {
3632                       /* icsEngineAnalyze */
3633                       if (appData.icsEngineAnalyze) {
3634                             ExitAnalyzeMode();
3635                             ModeHighlight();
3636                       }
3637                       StopClocks();
3638                       gameMode = IcsIdle;
3639                       ics_gamenum = -1;
3640                       ics_user_moved = FALSE;
3641                   }
3642                 continue;
3643             }
3644
3645             if (looking_at(buf, &i, "no longer examining game *")) {
3646                 if (gameMode == IcsExamining &&
3647                     atoi(star_match[0]) == ics_gamenum)
3648                   {
3649                       gameMode = IcsIdle;
3650                       ics_gamenum = -1;
3651                       ics_user_moved = FALSE;
3652                   }
3653                 continue;
3654             }
3655
3656             /* Advance leftover_start past any newlines we find,
3657                so only partial lines can get reparsed */
3658             if (looking_at(buf, &i, "\n")) {
3659                 prevColor = curColor;
3660                 if (curColor != ColorNormal) {
3661                     if (oldi > next_out) {
3662                         SendToPlayer(&buf[next_out], oldi - next_out);
3663                         next_out = oldi;
3664                     }
3665                     Colorize(ColorNormal, FALSE);
3666                     curColor = ColorNormal;
3667                 }
3668                 if (started == STARTED_BOARD) {
3669                     started = STARTED_NONE;
3670                     parse[parse_pos] = NULLCHAR;
3671                     ParseBoard12(parse);
3672                     ics_user_moved = 0;
3673
3674                     /* Send premove here */
3675                     if (appData.premove) {
3676                       char str[MSG_SIZ];
3677                       if (currentMove == 0 &&
3678                           gameMode == IcsPlayingWhite &&
3679                           appData.premoveWhite) {
3680                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3681                         if (appData.debugMode)
3682                           fprintf(debugFP, "Sending premove:\n");
3683                         SendToICS(str);
3684                       } else if (currentMove == 1 &&
3685                                  gameMode == IcsPlayingBlack &&
3686                                  appData.premoveBlack) {
3687                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3688                         if (appData.debugMode)
3689                           fprintf(debugFP, "Sending premove:\n");
3690                         SendToICS(str);
3691                       } else if (gotPremove) {
3692                         gotPremove = 0;
3693                         ClearPremoveHighlights();
3694                         if (appData.debugMode)
3695                           fprintf(debugFP, "Sending premove:\n");
3696                           UserMoveEvent(premoveFromX, premoveFromY,
3697                                         premoveToX, premoveToY,
3698                                         premovePromoChar);
3699                       }
3700                     }
3701
3702                     /* Usually suppress following prompt */
3703                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3704                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3705                         if (looking_at(buf, &i, "*% ")) {
3706                             savingComment = FALSE;
3707                             suppressKibitz = 0;
3708                         }
3709                     }
3710                     next_out = i;
3711                 } else if (started == STARTED_HOLDINGS) {
3712                     int gamenum;
3713                     char new_piece[MSG_SIZ];
3714                     started = STARTED_NONE;
3715                     parse[parse_pos] = NULLCHAR;
3716                     if (appData.debugMode)
3717                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3718                                                         parse, currentMove);
3719                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3720                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3721                         if (gameInfo.variant == VariantNormal) {
3722                           /* [HGM] We seem to switch variant during a game!
3723                            * Presumably no holdings were displayed, so we have
3724                            * to move the position two files to the right to
3725                            * create room for them!
3726                            */
3727                           VariantClass newVariant;
3728                           switch(gameInfo.boardWidth) { // base guess on board width
3729                                 case 9:  newVariant = VariantShogi; break;
3730                                 case 10: newVariant = VariantGreat; break;
3731                                 default: newVariant = VariantCrazyhouse; break;
3732                           }
3733                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3734                           /* Get a move list just to see the header, which
3735                              will tell us whether this is really bug or zh */
3736                           if (ics_getting_history == H_FALSE) {
3737                             ics_getting_history = H_REQUESTED;
3738                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3739                             SendToICS(str);
3740                           }
3741                         }
3742                         new_piece[0] = NULLCHAR;
3743                         sscanf(parse, "game %d white [%s black [%s <- %s",
3744                                &gamenum, white_holding, black_holding,
3745                                new_piece);
3746                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3747                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3748                         /* [HGM] copy holdings to board holdings area */
3749                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3750                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3751                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3752 #if ZIPPY
3753                         if (appData.zippyPlay && first.initDone) {
3754                             ZippyHoldings(white_holding, black_holding,
3755                                           new_piece);
3756                         }
3757 #endif /*ZIPPY*/
3758                         if (tinyLayout || smallLayout) {
3759                             char wh[16], bh[16];
3760                             PackHolding(wh, white_holding);
3761                             PackHolding(bh, black_holding);
3762                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3763                                     gameInfo.white, gameInfo.black);
3764                         } else {
3765                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3766                                     gameInfo.white, white_holding,
3767                                     gameInfo.black, black_holding);
3768                         }
3769                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3770                         DrawPosition(FALSE, boards[currentMove]);
3771                         DisplayTitle(str);
3772                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3773                         sscanf(parse, "game %d white [%s black [%s <- %s",
3774                                &gamenum, white_holding, black_holding,
3775                                new_piece);
3776                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3777                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3778                         /* [HGM] copy holdings to partner-board holdings area */
3779                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3780                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3781                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3782                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3783                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3784                       }
3785                     }
3786                     /* Suppress following prompt */
3787                     if (looking_at(buf, &i, "*% ")) {
3788                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3789                         savingComment = FALSE;
3790                         suppressKibitz = 0;
3791                     }
3792                     next_out = i;
3793                 }
3794                 continue;
3795             }
3796
3797             i++;                /* skip unparsed character and loop back */
3798         }
3799
3800         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3801 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3802 //          SendToPlayer(&buf[next_out], i - next_out);
3803             started != STARTED_HOLDINGS && leftover_start > next_out) {
3804             SendToPlayer(&buf[next_out], leftover_start - next_out);
3805             next_out = i;
3806         }
3807
3808         leftover_len = buf_len - leftover_start;
3809         /* if buffer ends with something we couldn't parse,
3810            reparse it after appending the next read */
3811
3812     } else if (count == 0) {
3813         RemoveInputSource(isr);
3814         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3815     } else {
3816         DisplayFatalError(_("Error reading from ICS"), error, 1);
3817     }
3818 }
3819
3820
3821 /* Board style 12 looks like this:
3822
3823    <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
3824
3825  * The "<12> " is stripped before it gets to this routine.  The two
3826  * trailing 0's (flip state and clock ticking) are later addition, and
3827  * some chess servers may not have them, or may have only the first.
3828  * Additional trailing fields may be added in the future.
3829  */
3830
3831 #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"
3832
3833 #define RELATION_OBSERVING_PLAYED    0
3834 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3835 #define RELATION_PLAYING_MYMOVE      1
3836 #define RELATION_PLAYING_NOTMYMOVE  -1
3837 #define RELATION_EXAMINING           2
3838 #define RELATION_ISOLATED_BOARD     -3
3839 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3840
3841 void
3842 ParseBoard12(string)
3843      char *string;
3844 {
3845     GameMode newGameMode;
3846     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3847     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3848     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3849     char to_play, board_chars[200];
3850     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3851     char black[32], white[32];
3852     Board board;
3853     int prevMove = currentMove;
3854     int ticking = 2;
3855     ChessMove moveType;
3856     int fromX, fromY, toX, toY;
3857     char promoChar;
3858     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3859     char *bookHit = NULL; // [HGM] book
3860     Boolean weird = FALSE, reqFlag = FALSE;
3861
3862     fromX = fromY = toX = toY = -1;
3863
3864     newGame = FALSE;
3865
3866     if (appData.debugMode)
3867       fprintf(debugFP, _("Parsing board: %s\n"), string);
3868
3869     move_str[0] = NULLCHAR;
3870     elapsed_time[0] = NULLCHAR;
3871     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3872         int  i = 0, j;
3873         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3874             if(string[i] == ' ') { ranks++; files = 0; }
3875             else files++;
3876             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3877             i++;
3878         }
3879         for(j = 0; j <i; j++) board_chars[j] = string[j];
3880         board_chars[i] = '\0';
3881         string += i + 1;
3882     }
3883     n = sscanf(string, PATTERN, &to_play, &double_push,
3884                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3885                &gamenum, white, black, &relation, &basetime, &increment,
3886                &white_stren, &black_stren, &white_time, &black_time,
3887                &moveNum, str, elapsed_time, move_str, &ics_flip,
3888                &ticking);
3889
3890     if (n < 21) {
3891         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3892         DisplayError(str, 0);
3893         return;
3894     }
3895
3896     /* Convert the move number to internal form */
3897     moveNum = (moveNum - 1) * 2;
3898     if (to_play == 'B') moveNum++;
3899     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3900       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3901                         0, 1);
3902       return;
3903     }
3904
3905     switch (relation) {
3906       case RELATION_OBSERVING_PLAYED:
3907       case RELATION_OBSERVING_STATIC:
3908         if (gamenum == -1) {
3909             /* Old ICC buglet */
3910             relation = RELATION_OBSERVING_STATIC;
3911         }
3912         newGameMode = IcsObserving;
3913         break;
3914       case RELATION_PLAYING_MYMOVE:
3915       case RELATION_PLAYING_NOTMYMOVE:
3916         newGameMode =
3917           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3918             IcsPlayingWhite : IcsPlayingBlack;
3919         break;
3920       case RELATION_EXAMINING:
3921         newGameMode = IcsExamining;
3922         break;
3923       case RELATION_ISOLATED_BOARD:
3924       default:
3925         /* Just display this board.  If user was doing something else,
3926            we will forget about it until the next board comes. */
3927         newGameMode = IcsIdle;
3928         break;
3929       case RELATION_STARTING_POSITION:
3930         newGameMode = gameMode;
3931         break;
3932     }
3933
3934     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3935          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3936       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3937       char *toSqr;
3938       for (k = 0; k < ranks; k++) {
3939         for (j = 0; j < files; j++)
3940           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3941         if(gameInfo.holdingsWidth > 1) {
3942              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3943              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3944         }
3945       }
3946       CopyBoard(partnerBoard, board);
3947       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3948         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3949         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3950       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3951       if(toSqr = strchr(str, '-')) {
3952         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3953         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3954       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3955       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3956       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3957       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3958       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3959       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3960                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3961       DisplayMessage(partnerStatus, "");
3962         partnerBoardValid = TRUE;
3963       return;
3964     }
3965
3966     /* Modify behavior for initial board display on move listing
3967        of wild games.
3968        */
3969     switch (ics_getting_history) {
3970       case H_FALSE:
3971       case H_REQUESTED:
3972         break;
3973       case H_GOT_REQ_HEADER:
3974       case H_GOT_UNREQ_HEADER:
3975         /* This is the initial position of the current game */
3976         gamenum = ics_gamenum;
3977         moveNum = 0;            /* old ICS bug workaround */
3978         if (to_play == 'B') {
3979           startedFromSetupPosition = TRUE;
3980           blackPlaysFirst = TRUE;
3981           moveNum = 1;
3982           if (forwardMostMove == 0) forwardMostMove = 1;
3983           if (backwardMostMove == 0) backwardMostMove = 1;
3984           if (currentMove == 0) currentMove = 1;
3985         }
3986         newGameMode = gameMode;
3987         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3988         break;
3989       case H_GOT_UNWANTED_HEADER:
3990         /* This is an initial board that we don't want */
3991         return;
3992       case H_GETTING_MOVES:
3993         /* Should not happen */
3994         DisplayError(_("Error gathering move list: extra board"), 0);
3995         ics_getting_history = H_FALSE;
3996         return;
3997     }
3998
3999    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4000                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4001      /* [HGM] We seem to have switched variant unexpectedly
4002       * Try to guess new variant from board size
4003       */
4004           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4005           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4006           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4007           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4008           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4009           if(!weird) newVariant = VariantNormal;
4010           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4011           /* Get a move list just to see the header, which
4012              will tell us whether this is really bug or zh */
4013           if (ics_getting_history == H_FALSE) {
4014             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4015             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4016             SendToICS(str);
4017           }
4018     }
4019
4020     /* Take action if this is the first board of a new game, or of a
4021        different game than is currently being displayed.  */
4022     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4023         relation == RELATION_ISOLATED_BOARD) {
4024
4025         /* Forget the old game and get the history (if any) of the new one */
4026         if (gameMode != BeginningOfGame) {
4027           Reset(TRUE, TRUE);
4028         }
4029         newGame = TRUE;
4030         if (appData.autoRaiseBoard) BoardToTop();
4031         prevMove = -3;
4032         if (gamenum == -1) {
4033             newGameMode = IcsIdle;
4034         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4035                    appData.getMoveList && !reqFlag) {
4036             /* Need to get game history */
4037             ics_getting_history = H_REQUESTED;
4038             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4039             SendToICS(str);
4040         }
4041
4042         /* Initially flip the board to have black on the bottom if playing
4043            black or if the ICS flip flag is set, but let the user change
4044            it with the Flip View button. */
4045         flipView = appData.autoFlipView ?
4046           (newGameMode == IcsPlayingBlack) || ics_flip :
4047           appData.flipView;
4048
4049         /* Done with values from previous mode; copy in new ones */
4050         gameMode = newGameMode;
4051         ModeHighlight();
4052         ics_gamenum = gamenum;
4053         if (gamenum == gs_gamenum) {
4054             int klen = strlen(gs_kind);
4055             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4056             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4057             gameInfo.event = StrSave(str);
4058         } else {
4059             gameInfo.event = StrSave("ICS game");
4060         }
4061         gameInfo.site = StrSave(appData.icsHost);
4062         gameInfo.date = PGNDate();
4063         gameInfo.round = StrSave("-");
4064         gameInfo.white = StrSave(white);
4065         gameInfo.black = StrSave(black);
4066         timeControl = basetime * 60 * 1000;
4067         timeControl_2 = 0;
4068         timeIncrement = increment * 1000;
4069         movesPerSession = 0;
4070         gameInfo.timeControl = TimeControlTagValue();
4071         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4072   if (appData.debugMode) {
4073     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4074     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4075     setbuf(debugFP, NULL);
4076   }
4077
4078         gameInfo.outOfBook = NULL;
4079
4080         /* Do we have the ratings? */
4081         if (strcmp(player1Name, white) == 0 &&
4082             strcmp(player2Name, black) == 0) {
4083             if (appData.debugMode)
4084               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4085                       player1Rating, player2Rating);
4086             gameInfo.whiteRating = player1Rating;
4087             gameInfo.blackRating = player2Rating;
4088         } else if (strcmp(player2Name, white) == 0 &&
4089                    strcmp(player1Name, black) == 0) {
4090             if (appData.debugMode)
4091               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4092                       player2Rating, player1Rating);
4093             gameInfo.whiteRating = player2Rating;
4094             gameInfo.blackRating = player1Rating;
4095         }
4096         player1Name[0] = player2Name[0] = NULLCHAR;
4097
4098         /* Silence shouts if requested */
4099         if (appData.quietPlay &&
4100             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4101             SendToICS(ics_prefix);
4102             SendToICS("set shout 0\n");
4103         }
4104     }
4105
4106     /* Deal with midgame name changes */
4107     if (!newGame) {
4108         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4109             if (gameInfo.white) free(gameInfo.white);
4110             gameInfo.white = StrSave(white);
4111         }
4112         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4113             if (gameInfo.black) free(gameInfo.black);
4114             gameInfo.black = StrSave(black);
4115         }
4116     }
4117
4118     /* Throw away game result if anything actually changes in examine mode */
4119     if (gameMode == IcsExamining && !newGame) {
4120         gameInfo.result = GameUnfinished;
4121         if (gameInfo.resultDetails != NULL) {
4122             free(gameInfo.resultDetails);
4123             gameInfo.resultDetails = NULL;
4124         }
4125     }
4126
4127     /* In pausing && IcsExamining mode, we ignore boards coming
4128        in if they are in a different variation than we are. */
4129     if (pauseExamInvalid) return;
4130     if (pausing && gameMode == IcsExamining) {
4131         if (moveNum <= pauseExamForwardMostMove) {
4132             pauseExamInvalid = TRUE;
4133             forwardMostMove = pauseExamForwardMostMove;
4134             return;
4135         }
4136     }
4137
4138   if (appData.debugMode) {
4139     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4140   }
4141     /* Parse the board */
4142     for (k = 0; k < ranks; k++) {
4143       for (j = 0; j < files; j++)
4144         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4145       if(gameInfo.holdingsWidth > 1) {
4146            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4147            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4148       }
4149     }
4150     CopyBoard(boards[moveNum], board);
4151     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4152     if (moveNum == 0) {
4153         startedFromSetupPosition =
4154           !CompareBoards(board, initialPosition);
4155         if(startedFromSetupPosition)
4156             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4157     }
4158
4159     /* [HGM] Set castling rights. Take the outermost Rooks,
4160        to make it also work for FRC opening positions. Note that board12
4161        is really defective for later FRC positions, as it has no way to
4162        indicate which Rook can castle if they are on the same side of King.
4163        For the initial position we grant rights to the outermost Rooks,
4164        and remember thos rights, and we then copy them on positions
4165        later in an FRC game. This means WB might not recognize castlings with
4166        Rooks that have moved back to their original position as illegal,
4167        but in ICS mode that is not its job anyway.
4168     */
4169     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4170     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4171
4172         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4173             if(board[0][i] == WhiteRook) j = i;
4174         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4175         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4176             if(board[0][i] == WhiteRook) j = i;
4177         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4178         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4179             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4180         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4181         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4182             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4183         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4184
4185         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4186         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4187             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4188         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4189             if(board[BOARD_HEIGHT-1][k] == bKing)
4190                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4191         if(gameInfo.variant == VariantTwoKings) {
4192             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4193             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4194             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4195         }
4196     } else { int r;
4197         r = boards[moveNum][CASTLING][0] = initialRights[0];
4198         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4199         r = boards[moveNum][CASTLING][1] = initialRights[1];
4200         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4201         r = boards[moveNum][CASTLING][3] = initialRights[3];
4202         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4203         r = boards[moveNum][CASTLING][4] = initialRights[4];
4204         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4205         /* wildcastle kludge: always assume King has rights */
4206         r = boards[moveNum][CASTLING][2] = initialRights[2];
4207         r = boards[moveNum][CASTLING][5] = initialRights[5];
4208     }
4209     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4210     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4211
4212
4213     if (ics_getting_history == H_GOT_REQ_HEADER ||
4214         ics_getting_history == H_GOT_UNREQ_HEADER) {
4215         /* This was an initial position from a move list, not
4216            the current position */
4217         return;
4218     }
4219
4220     /* Update currentMove and known move number limits */
4221     newMove = newGame || moveNum > forwardMostMove;
4222
4223     if (newGame) {
4224         forwardMostMove = backwardMostMove = currentMove = moveNum;
4225         if (gameMode == IcsExamining && moveNum == 0) {
4226           /* Workaround for ICS limitation: we are not told the wild
4227              type when starting to examine a game.  But if we ask for
4228              the move list, the move list header will tell us */
4229             ics_getting_history = H_REQUESTED;
4230             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4231             SendToICS(str);
4232         }
4233     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4234                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4235 #if ZIPPY
4236         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4237         /* [HGM] applied this also to an engine that is silently watching        */
4238         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4239             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4240             gameInfo.variant == currentlyInitializedVariant) {
4241           takeback = forwardMostMove - moveNum;
4242           for (i = 0; i < takeback; i++) {
4243             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4244             SendToProgram("undo\n", &first);
4245           }
4246         }
4247 #endif
4248
4249         forwardMostMove = moveNum;
4250         if (!pausing || currentMove > forwardMostMove)
4251           currentMove = forwardMostMove;
4252     } else {
4253         /* New part of history that is not contiguous with old part */
4254         if (pausing && gameMode == IcsExamining) {
4255             pauseExamInvalid = TRUE;
4256             forwardMostMove = pauseExamForwardMostMove;
4257             return;
4258         }
4259         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4260 #if ZIPPY
4261             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4262                 // [HGM] when we will receive the move list we now request, it will be
4263                 // fed to the engine from the first move on. So if the engine is not
4264                 // in the initial position now, bring it there.
4265                 InitChessProgram(&first, 0);
4266             }
4267 #endif
4268             ics_getting_history = H_REQUESTED;
4269             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4270             SendToICS(str);
4271         }
4272         forwardMostMove = backwardMostMove = currentMove = moveNum;
4273     }
4274
4275     /* Update the clocks */
4276     if (strchr(elapsed_time, '.')) {
4277       /* Time is in ms */
4278       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4279       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4280     } else {
4281       /* Time is in seconds */
4282       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4283       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4284     }
4285
4286
4287 #if ZIPPY
4288     if (appData.zippyPlay && newGame &&
4289         gameMode != IcsObserving && gameMode != IcsIdle &&
4290         gameMode != IcsExamining)
4291       ZippyFirstBoard(moveNum, basetime, increment);
4292 #endif
4293
4294     /* Put the move on the move list, first converting
4295        to canonical algebraic form. */
4296     if (moveNum > 0) {
4297   if (appData.debugMode) {
4298     if (appData.debugMode) { int f = forwardMostMove;
4299         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4300                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4301                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4302     }
4303     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4304     fprintf(debugFP, "moveNum = %d\n", moveNum);
4305     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4306     setbuf(debugFP, NULL);
4307   }
4308         if (moveNum <= backwardMostMove) {
4309             /* We don't know what the board looked like before
4310                this move.  Punt. */
4311           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4312             strcat(parseList[moveNum - 1], " ");
4313             strcat(parseList[moveNum - 1], elapsed_time);
4314             moveList[moveNum - 1][0] = NULLCHAR;
4315         } else if (strcmp(move_str, "none") == 0) {
4316             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4317             /* Again, we don't know what the board looked like;
4318                this is really the start of the game. */
4319             parseList[moveNum - 1][0] = NULLCHAR;
4320             moveList[moveNum - 1][0] = NULLCHAR;
4321             backwardMostMove = moveNum;
4322             startedFromSetupPosition = TRUE;
4323             fromX = fromY = toX = toY = -1;
4324         } else {
4325           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4326           //                 So we parse the long-algebraic move string in stead of the SAN move
4327           int valid; char buf[MSG_SIZ], *prom;
4328
4329           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4330                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4331           // str looks something like "Q/a1-a2"; kill the slash
4332           if(str[1] == '/')
4333             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4334           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4335           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4336                 strcat(buf, prom); // long move lacks promo specification!
4337           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4338                 if(appData.debugMode)
4339                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4340                 safeStrCpy(move_str, buf, MSG_SIZ);
4341           }
4342           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4343                                 &fromX, &fromY, &toX, &toY, &promoChar)
4344                || ParseOneMove(buf, moveNum - 1, &moveType,
4345                                 &fromX, &fromY, &toX, &toY, &promoChar);
4346           // end of long SAN patch
4347           if (valid) {
4348             (void) CoordsToAlgebraic(boards[moveNum - 1],
4349                                      PosFlags(moveNum - 1),
4350                                      fromY, fromX, toY, toX, promoChar,
4351                                      parseList[moveNum-1]);
4352             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4353               case MT_NONE:
4354               case MT_STALEMATE:
4355               default:
4356                 break;
4357               case MT_CHECK:
4358                 if(gameInfo.variant != VariantShogi)
4359                     strcat(parseList[moveNum - 1], "+");
4360                 break;
4361               case MT_CHECKMATE:
4362               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4363                 strcat(parseList[moveNum - 1], "#");
4364                 break;
4365             }
4366             strcat(parseList[moveNum - 1], " ");
4367             strcat(parseList[moveNum - 1], elapsed_time);
4368             /* currentMoveString is set as a side-effect of ParseOneMove */
4369             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4370             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4371             strcat(moveList[moveNum - 1], "\n");
4372
4373             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4374               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4375                 ChessSquare old, new = boards[moveNum][k][j];
4376                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4377                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4378                   if(old == new) continue;
4379                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4380                   else if(new == WhiteWazir || new == BlackWazir) {
4381                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4382                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4383                       else boards[moveNum][k][j] = old; // preserve type of Gold
4384                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4385                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4386               }
4387           } else {
4388             /* Move from ICS was illegal!?  Punt. */
4389             if (appData.debugMode) {
4390               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4391               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4392             }
4393             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4394             strcat(parseList[moveNum - 1], " ");
4395             strcat(parseList[moveNum - 1], elapsed_time);
4396             moveList[moveNum - 1][0] = NULLCHAR;
4397             fromX = fromY = toX = toY = -1;
4398           }
4399         }
4400   if (appData.debugMode) {
4401     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4402     setbuf(debugFP, NULL);
4403   }
4404
4405 #if ZIPPY
4406         /* Send move to chess program (BEFORE animating it). */
4407         if (appData.zippyPlay && !newGame && newMove &&
4408            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4409
4410             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4411                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4412                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4413                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4414                             move_str);
4415                     DisplayError(str, 0);
4416                 } else {
4417                     if (first.sendTime) {
4418                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4419                     }
4420                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4421                     if (firstMove && !bookHit) {
4422                         firstMove = FALSE;
4423                         if (first.useColors) {
4424                           SendToProgram(gameMode == IcsPlayingWhite ?
4425                                         "white\ngo\n" :
4426                                         "black\ngo\n", &first);
4427                         } else {
4428                           SendToProgram("go\n", &first);
4429                         }
4430                         first.maybeThinking = TRUE;
4431                     }
4432                 }
4433             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4434               if (moveList[moveNum - 1][0] == NULLCHAR) {
4435                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4436                 DisplayError(str, 0);
4437               } else {
4438                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4439                 SendMoveToProgram(moveNum - 1, &first);
4440               }
4441             }
4442         }
4443 #endif
4444     }
4445
4446     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4447         /* If move comes from a remote source, animate it.  If it
4448            isn't remote, it will have already been animated. */
4449         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4450             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4451         }
4452         if (!pausing && appData.highlightLastMove) {
4453             SetHighlights(fromX, fromY, toX, toY);
4454         }
4455     }
4456
4457     /* Start the clocks */
4458     whiteFlag = blackFlag = FALSE;
4459     appData.clockMode = !(basetime == 0 && increment == 0);
4460     if (ticking == 0) {
4461       ics_clock_paused = TRUE;
4462       StopClocks();
4463     } else if (ticking == 1) {
4464       ics_clock_paused = FALSE;
4465     }
4466     if (gameMode == IcsIdle ||
4467         relation == RELATION_OBSERVING_STATIC ||
4468         relation == RELATION_EXAMINING ||
4469         ics_clock_paused)
4470       DisplayBothClocks();
4471     else
4472       StartClocks();
4473
4474     /* Display opponents and material strengths */
4475     if (gameInfo.variant != VariantBughouse &&
4476         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4477         if (tinyLayout || smallLayout) {
4478             if(gameInfo.variant == VariantNormal)
4479               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4480                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4481                     basetime, increment);
4482             else
4483               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4484                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4485                     basetime, increment, (int) gameInfo.variant);
4486         } else {
4487             if(gameInfo.variant == VariantNormal)
4488               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4489                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4490                     basetime, increment);
4491             else
4492               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4493                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4494                     basetime, increment, VariantName(gameInfo.variant));
4495         }
4496         DisplayTitle(str);
4497   if (appData.debugMode) {
4498     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4499   }
4500     }
4501
4502
4503     /* Display the board */
4504     if (!pausing && !appData.noGUI) {
4505
4506       if (appData.premove)
4507           if (!gotPremove ||
4508              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4509              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4510               ClearPremoveHighlights();
4511
4512       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4513         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4514       DrawPosition(j, boards[currentMove]);
4515
4516       DisplayMove(moveNum - 1);
4517       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4518             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4519               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4520         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4521       }
4522     }
4523
4524     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4525 #if ZIPPY
4526     if(bookHit) { // [HGM] book: simulate book reply
4527         static char bookMove[MSG_SIZ]; // a bit generous?
4528
4529         programStats.nodes = programStats.depth = programStats.time =
4530         programStats.score = programStats.got_only_move = 0;
4531         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4532
4533         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4534         strcat(bookMove, bookHit);
4535         HandleMachineMove(bookMove, &first);
4536     }
4537 #endif
4538 }
4539
4540 void
4541 GetMoveListEvent()
4542 {
4543     char buf[MSG_SIZ];
4544     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4545         ics_getting_history = H_REQUESTED;
4546         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4547         SendToICS(buf);
4548     }
4549 }
4550
4551 void
4552 AnalysisPeriodicEvent(force)
4553      int force;
4554 {
4555     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4556          && !force) || !appData.periodicUpdates)
4557       return;
4558
4559     /* Send . command to Crafty to collect stats */
4560     SendToProgram(".\n", &first);
4561
4562     /* Don't send another until we get a response (this makes
4563        us stop sending to old Crafty's which don't understand
4564        the "." command (sending illegal cmds resets node count & time,
4565        which looks bad)) */
4566     programStats.ok_to_send = 0;
4567 }
4568
4569 void ics_update_width(new_width)
4570         int new_width;
4571 {
4572         ics_printf("set width %d\n", new_width);
4573 }
4574
4575 void
4576 SendMoveToProgram(moveNum, cps)
4577      int moveNum;
4578      ChessProgramState *cps;
4579 {
4580     char buf[MSG_SIZ];
4581
4582     if (cps->useUsermove) {
4583       SendToProgram("usermove ", cps);
4584     }
4585     if (cps->useSAN) {
4586       char *space;
4587       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4588         int len = space - parseList[moveNum];
4589         memcpy(buf, parseList[moveNum], len);
4590         buf[len++] = '\n';
4591         buf[len] = NULLCHAR;
4592       } else {
4593         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4594       }
4595       SendToProgram(buf, cps);
4596     } else {
4597       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4598         AlphaRank(moveList[moveNum], 4);
4599         SendToProgram(moveList[moveNum], cps);
4600         AlphaRank(moveList[moveNum], 4); // and back
4601       } else
4602       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4603        * the engine. It would be nice to have a better way to identify castle
4604        * moves here. */
4605       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4606                                                                          && cps->useOOCastle) {
4607         int fromX = moveList[moveNum][0] - AAA;
4608         int fromY = moveList[moveNum][1] - ONE;
4609         int toX = moveList[moveNum][2] - AAA;
4610         int toY = moveList[moveNum][3] - ONE;
4611         if((boards[moveNum][fromY][fromX] == WhiteKing
4612             && boards[moveNum][toY][toX] == WhiteRook)
4613            || (boards[moveNum][fromY][fromX] == BlackKing
4614                && boards[moveNum][toY][toX] == BlackRook)) {
4615           if(toX > fromX) SendToProgram("O-O\n", cps);
4616           else SendToProgram("O-O-O\n", cps);
4617         }
4618         else SendToProgram(moveList[moveNum], cps);
4619       }
4620       else SendToProgram(moveList[moveNum], cps);
4621       /* End of additions by Tord */
4622     }
4623
4624     /* [HGM] setting up the opening has brought engine in force mode! */
4625     /*       Send 'go' if we are in a mode where machine should play. */
4626     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4627         (gameMode == TwoMachinesPlay   ||
4628 #if ZIPPY
4629          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4630 #endif
4631          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4632         SendToProgram("go\n", cps);
4633   if (appData.debugMode) {
4634     fprintf(debugFP, "(extra)\n");
4635   }
4636     }
4637     setboardSpoiledMachineBlack = 0;
4638 }
4639
4640 void
4641 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4642      ChessMove moveType;
4643      int fromX, fromY, toX, toY;
4644      char promoChar;
4645 {
4646     char user_move[MSG_SIZ];
4647
4648     switch (moveType) {
4649       default:
4650         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4651                 (int)moveType, fromX, fromY, toX, toY);
4652         DisplayError(user_move + strlen("say "), 0);
4653         break;
4654       case WhiteKingSideCastle:
4655       case BlackKingSideCastle:
4656       case WhiteQueenSideCastleWild:
4657       case BlackQueenSideCastleWild:
4658       /* PUSH Fabien */
4659       case WhiteHSideCastleFR:
4660       case BlackHSideCastleFR:
4661       /* POP Fabien */
4662         snprintf(user_move, MSG_SIZ, "o-o\n");
4663         break;
4664       case WhiteQueenSideCastle:
4665       case BlackQueenSideCastle:
4666       case WhiteKingSideCastleWild:
4667       case BlackKingSideCastleWild:
4668       /* PUSH Fabien */
4669       case WhiteASideCastleFR:
4670       case BlackASideCastleFR:
4671       /* POP Fabien */
4672         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4673         break;
4674       case WhiteNonPromotion:
4675       case BlackNonPromotion:
4676         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4677         break;
4678       case WhitePromotion:
4679       case BlackPromotion:
4680         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4681           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4682                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4683                 PieceToChar(WhiteFerz));
4684         else if(gameInfo.variant == VariantGreat)
4685           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4686                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4687                 PieceToChar(WhiteMan));
4688         else
4689           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4690                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4691                 promoChar);
4692         break;
4693       case WhiteDrop:
4694       case BlackDrop:
4695       drop:
4696         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4697                  ToUpper(PieceToChar((ChessSquare) fromX)),
4698                  AAA + toX, ONE + toY);
4699         break;
4700       case IllegalMove:  /* could be a variant we don't quite understand */
4701         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4702       case NormalMove:
4703       case WhiteCapturesEnPassant:
4704       case BlackCapturesEnPassant:
4705         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4706                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4707         break;
4708     }
4709     SendToICS(user_move);
4710     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4711         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4712 }
4713
4714 void
4715 UploadGameEvent()
4716 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4717     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4718     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4719     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4720         DisplayError("You cannot do this while you are playing or observing", 0);
4721         return;
4722     }
4723     if(gameMode != IcsExamining) { // is this ever not the case?
4724         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4725
4726         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4727           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4728         } else { // on FICS we must first go to general examine mode
4729           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4730         }
4731         if(gameInfo.variant != VariantNormal) {
4732             // try figure out wild number, as xboard names are not always valid on ICS
4733             for(i=1; i<=36; i++) {
4734               snprintf(buf, MSG_SIZ, "wild/%d", i);
4735                 if(StringToVariant(buf) == gameInfo.variant) break;
4736             }
4737             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4738             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4739             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4740         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4741         SendToICS(ics_prefix);
4742         SendToICS(buf);
4743         if(startedFromSetupPosition || backwardMostMove != 0) {
4744           fen = PositionToFEN(backwardMostMove, NULL);
4745           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4746             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4747             SendToICS(buf);
4748           } else { // FICS: everything has to set by separate bsetup commands
4749             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4750             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4751             SendToICS(buf);
4752             if(!WhiteOnMove(backwardMostMove)) {
4753                 SendToICS("bsetup tomove black\n");
4754             }
4755             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4756             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4757             SendToICS(buf);
4758             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4759             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4760             SendToICS(buf);
4761             i = boards[backwardMostMove][EP_STATUS];
4762             if(i >= 0) { // set e.p.
4763               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4764                 SendToICS(buf);
4765             }
4766             bsetup++;
4767           }
4768         }
4769       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4770             SendToICS("bsetup done\n"); // switch to normal examining.
4771     }
4772     for(i = backwardMostMove; i<last; i++) {
4773         char buf[20];
4774         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4775         SendToICS(buf);
4776     }
4777     SendToICS(ics_prefix);
4778     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4779 }
4780
4781 void
4782 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4783      int rf, ff, rt, ft;
4784      char promoChar;
4785      char move[7];
4786 {
4787     if (rf == DROP_RANK) {
4788       sprintf(move, "%c@%c%c\n",
4789                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4790     } else {
4791         if (promoChar == 'x' || promoChar == NULLCHAR) {
4792           sprintf(move, "%c%c%c%c\n",
4793                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4794         } else {
4795             sprintf(move, "%c%c%c%c%c\n",
4796                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4797         }
4798     }
4799 }
4800
4801 void
4802 ProcessICSInitScript(f)
4803      FILE *f;
4804 {
4805     char buf[MSG_SIZ];
4806
4807     while (fgets(buf, MSG_SIZ, f)) {
4808         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4809     }
4810
4811     fclose(f);
4812 }
4813
4814
4815 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4816 void
4817 AlphaRank(char *move, int n)
4818 {
4819 //    char *p = move, c; int x, y;
4820
4821     if (appData.debugMode) {
4822         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4823     }
4824
4825     if(move[1]=='*' &&
4826        move[2]>='0' && move[2]<='9' &&
4827        move[3]>='a' && move[3]<='x'    ) {
4828         move[1] = '@';
4829         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4830         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4831     } else
4832     if(move[0]>='0' && move[0]<='9' &&
4833        move[1]>='a' && move[1]<='x' &&
4834        move[2]>='0' && move[2]<='9' &&
4835        move[3]>='a' && move[3]<='x'    ) {
4836         /* input move, Shogi -> normal */
4837         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4838         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4839         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4840         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4841     } else
4842     if(move[1]=='@' &&
4843        move[3]>='0' && move[3]<='9' &&
4844        move[2]>='a' && move[2]<='x'    ) {
4845         move[1] = '*';
4846         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4847         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4848     } else
4849     if(
4850        move[0]>='a' && move[0]<='x' &&
4851        move[3]>='0' && move[3]<='9' &&
4852        move[2]>='a' && move[2]<='x'    ) {
4853          /* output move, normal -> Shogi */
4854         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4855         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4856         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4857         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4858         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4859     }
4860     if (appData.debugMode) {
4861         fprintf(debugFP, "   out = '%s'\n", move);
4862     }
4863 }
4864
4865 char yy_textstr[8000];
4866
4867 /* Parser for moves from gnuchess, ICS, or user typein box */
4868 Boolean
4869 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4870      char *move;
4871      int moveNum;
4872      ChessMove *moveType;
4873      int *fromX, *fromY, *toX, *toY;
4874      char *promoChar;
4875 {
4876     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4877
4878     switch (*moveType) {
4879       case WhitePromotion:
4880       case BlackPromotion:
4881       case WhiteNonPromotion:
4882       case BlackNonPromotion:
4883       case NormalMove:
4884       case WhiteCapturesEnPassant:
4885       case BlackCapturesEnPassant:
4886       case WhiteKingSideCastle:
4887       case WhiteQueenSideCastle:
4888       case BlackKingSideCastle:
4889       case BlackQueenSideCastle:
4890       case WhiteKingSideCastleWild:
4891       case WhiteQueenSideCastleWild:
4892       case BlackKingSideCastleWild:
4893       case BlackQueenSideCastleWild:
4894       /* Code added by Tord: */
4895       case WhiteHSideCastleFR:
4896       case WhiteASideCastleFR:
4897       case BlackHSideCastleFR:
4898       case BlackASideCastleFR:
4899       /* End of code added by Tord */
4900       case IllegalMove:         /* bug or odd chess variant */
4901         *fromX = currentMoveString[0] - AAA;
4902         *fromY = currentMoveString[1] - ONE;
4903         *toX = currentMoveString[2] - AAA;
4904         *toY = currentMoveString[3] - ONE;
4905         *promoChar = currentMoveString[4];
4906         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4907             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4908     if (appData.debugMode) {
4909         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4910     }
4911             *fromX = *fromY = *toX = *toY = 0;
4912             return FALSE;
4913         }
4914         if (appData.testLegality) {
4915           return (*moveType != IllegalMove);
4916         } else {
4917           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4918                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4919         }
4920
4921       case WhiteDrop:
4922       case BlackDrop:
4923         *fromX = *moveType == WhiteDrop ?
4924           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4925           (int) CharToPiece(ToLower(currentMoveString[0]));
4926         *fromY = DROP_RANK;
4927         *toX = currentMoveString[2] - AAA;
4928         *toY = currentMoveString[3] - ONE;
4929         *promoChar = NULLCHAR;
4930         return TRUE;
4931
4932       case AmbiguousMove:
4933       case ImpossibleMove:
4934       case EndOfFile:
4935       case ElapsedTime:
4936       case Comment:
4937       case PGNTag:
4938       case NAG:
4939       case WhiteWins:
4940       case BlackWins:
4941       case GameIsDrawn:
4942       default:
4943     if (appData.debugMode) {
4944         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4945     }
4946         /* bug? */
4947         *fromX = *fromY = *toX = *toY = 0;
4948         *promoChar = NULLCHAR;
4949         return FALSE;
4950     }
4951 }
4952
4953
4954 void
4955 ParsePV(char *pv, Boolean storeComments)
4956 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4957   int fromX, fromY, toX, toY; char promoChar;
4958   ChessMove moveType;
4959   Boolean valid;
4960   int nr = 0;
4961
4962   endPV = forwardMostMove;
4963   do {
4964     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4965     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4966     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4967 if(appData.debugMode){
4968 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);
4969 }
4970     if(!valid && nr == 0 &&
4971        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4972         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4973         // Hande case where played move is different from leading PV move
4974         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4975         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4976         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4977         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4978           endPV += 2; // if position different, keep this
4979           moveList[endPV-1][0] = fromX + AAA;
4980           moveList[endPV-1][1] = fromY + ONE;
4981           moveList[endPV-1][2] = toX + AAA;
4982           moveList[endPV-1][3] = toY + ONE;
4983           parseList[endPV-1][0] = NULLCHAR;
4984           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4985         }
4986       }
4987     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4988     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4989     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4990     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4991         valid++; // allow comments in PV
4992         continue;
4993     }
4994     nr++;
4995     if(endPV+1 > framePtr) break; // no space, truncate
4996     if(!valid) break;
4997     endPV++;
4998     CopyBoard(boards[endPV], boards[endPV-1]);
4999     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5000     moveList[endPV-1][0] = fromX + AAA;
5001     moveList[endPV-1][1] = fromY + ONE;
5002     moveList[endPV-1][2] = toX + AAA;
5003     moveList[endPV-1][3] = toY + ONE;
5004     if(storeComments)
5005         CoordsToAlgebraic(boards[endPV - 1],
5006                              PosFlags(endPV - 1),
5007                              fromY, fromX, toY, toX, promoChar,
5008                              parseList[endPV - 1]);
5009     else
5010         parseList[endPV-1][0] = NULLCHAR;
5011   } while(valid);
5012   currentMove = endPV;
5013   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5014   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5015                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5016   DrawPosition(TRUE, boards[currentMove]);
5017 }
5018
5019 static int lastX, lastY;
5020
5021 Boolean
5022 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5023 {
5024         int startPV;
5025         char *p;
5026
5027         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5028         lastX = x; lastY = y;
5029         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5030         startPV = index;
5031         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5032         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5033         index = startPV;
5034         do{ while(buf[index] && buf[index] != '\n') index++;
5035         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5036         buf[index] = 0;
5037         ParsePV(buf+startPV, FALSE);
5038         *start = startPV; *end = index-1;
5039         return TRUE;
5040 }
5041
5042 Boolean
5043 LoadPV(int x, int y)
5044 { // called on right mouse click to load PV
5045   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5046   lastX = x; lastY = y;
5047   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5048   return TRUE;
5049 }
5050
5051 void
5052 UnLoadPV()
5053 {
5054   if(endPV < 0) return;
5055   endPV = -1;
5056   currentMove = forwardMostMove;
5057   ClearPremoveHighlights();
5058   DrawPosition(TRUE, boards[currentMove]);
5059 }
5060
5061 void
5062 MovePV(int x, int y, int h)
5063 { // step through PV based on mouse coordinates (called on mouse move)
5064   int margin = h>>3, step = 0;
5065
5066   if(endPV < 0) return;
5067   // we must somehow check if right button is still down (might be released off board!)
5068   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5069   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5070   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5071   if(!step) return;
5072   lastX = x; lastY = y;
5073   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5074   currentMove += step;
5075   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5076   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5077                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5078   DrawPosition(FALSE, boards[currentMove]);
5079 }
5080
5081
5082 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5083 // All positions will have equal probability, but the current method will not provide a unique
5084 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5085 #define DARK 1
5086 #define LITE 2
5087 #define ANY 3
5088
5089 int squaresLeft[4];
5090 int piecesLeft[(int)BlackPawn];
5091 int seed, nrOfShuffles;
5092
5093 void GetPositionNumber()
5094 {       // sets global variable seed
5095         int i;
5096
5097         seed = appData.defaultFrcPosition;
5098         if(seed < 0) { // randomize based on time for negative FRC position numbers
5099                 for(i=0; i<50; i++) seed += random();
5100                 seed = random() ^ random() >> 8 ^ random() << 8;
5101                 if(seed<0) seed = -seed;
5102         }
5103 }
5104
5105 int put(Board board, int pieceType, int rank, int n, int shade)
5106 // put the piece on the (n-1)-th empty squares of the given shade
5107 {
5108         int i;
5109
5110         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5111                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5112                         board[rank][i] = (ChessSquare) pieceType;
5113                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5114                         squaresLeft[ANY]--;
5115                         piecesLeft[pieceType]--;
5116                         return i;
5117                 }
5118         }
5119         return -1;
5120 }
5121
5122
5123 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5124 // calculate where the next piece goes, (any empty square), and put it there
5125 {
5126         int i;
5127
5128         i = seed % squaresLeft[shade];
5129         nrOfShuffles *= squaresLeft[shade];
5130         seed /= squaresLeft[shade];
5131         put(board, pieceType, rank, i, shade);
5132 }
5133
5134 void AddTwoPieces(Board board, int pieceType, int rank)
5135 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5136 {
5137         int i, n=squaresLeft[ANY], j=n-1, k;
5138
5139         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5140         i = seed % k;  // pick one
5141         nrOfShuffles *= k;
5142         seed /= k;
5143         while(i >= j) i -= j--;
5144         j = n - 1 - j; i += j;
5145         put(board, pieceType, rank, j, ANY);
5146         put(board, pieceType, rank, i, ANY);
5147 }
5148
5149 void SetUpShuffle(Board board, int number)
5150 {
5151         int i, p, first=1;
5152
5153         GetPositionNumber(); nrOfShuffles = 1;
5154
5155         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5156         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5157         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5158
5159         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5160
5161         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5162             p = (int) board[0][i];
5163             if(p < (int) BlackPawn) piecesLeft[p] ++;
5164             board[0][i] = EmptySquare;
5165         }
5166
5167         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5168             // shuffles restricted to allow normal castling put KRR first
5169             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5170                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5171             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5172                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5173             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5174                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5175             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5176                 put(board, WhiteRook, 0, 0, ANY);
5177             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5178         }
5179
5180         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5181             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5182             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5183                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5184                 while(piecesLeft[p] >= 2) {
5185                     AddOnePiece(board, p, 0, LITE);
5186                     AddOnePiece(board, p, 0, DARK);
5187                 }
5188                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5189             }
5190
5191         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5192             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5193             // but we leave King and Rooks for last, to possibly obey FRC restriction
5194             if(p == (int)WhiteRook) continue;
5195             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5196             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5197         }
5198
5199         // now everything is placed, except perhaps King (Unicorn) and Rooks
5200
5201         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5202             // Last King gets castling rights
5203             while(piecesLeft[(int)WhiteUnicorn]) {
5204                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5205                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5206             }
5207
5208             while(piecesLeft[(int)WhiteKing]) {
5209                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5210                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5211             }
5212
5213
5214         } else {
5215             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5216             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5217         }
5218
5219         // Only Rooks can be left; simply place them all
5220         while(piecesLeft[(int)WhiteRook]) {
5221                 i = put(board, WhiteRook, 0, 0, ANY);
5222                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5223                         if(first) {
5224                                 first=0;
5225                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5226                         }
5227                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5228                 }
5229         }
5230         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5231             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5232         }
5233
5234         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5235 }
5236
5237 int SetCharTable( char *table, const char * map )
5238 /* [HGM] moved here from winboard.c because of its general usefulness */
5239 /*       Basically a safe strcpy that uses the last character as King */
5240 {
5241     int result = FALSE; int NrPieces;
5242
5243     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5244                     && NrPieces >= 12 && !(NrPieces&1)) {
5245         int i; /* [HGM] Accept even length from 12 to 34 */
5246
5247         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5248         for( i=0; i<NrPieces/2-1; i++ ) {
5249             table[i] = map[i];
5250             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5251         }
5252         table[(int) WhiteKing]  = map[NrPieces/2-1];
5253         table[(int) BlackKing]  = map[NrPieces-1];
5254
5255         result = TRUE;
5256     }
5257
5258     return result;
5259 }
5260
5261 void Prelude(Board board)
5262 {       // [HGM] superchess: random selection of exo-pieces
5263         int i, j, k; ChessSquare p;
5264         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5265
5266         GetPositionNumber(); // use FRC position number
5267
5268         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5269             SetCharTable(pieceToChar, appData.pieceToCharTable);
5270             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5271                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5272         }
5273
5274         j = seed%4;                 seed /= 4;
5275         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5276         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5277         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5278         j = seed%3 + (seed%3 >= j); seed /= 3;
5279         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5280         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5281         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5282         j = seed%3;                 seed /= 3;
5283         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%2 + (seed%2 >= j); seed /= 2;
5287         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = 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%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5291         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5292         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5293         put(board, exoPieces[0],    0, 0, ANY);
5294         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5295 }
5296
5297 void
5298 InitPosition(redraw)
5299      int redraw;
5300 {
5301     ChessSquare (* pieces)[BOARD_FILES];
5302     int i, j, pawnRow, overrule,
5303     oldx = gameInfo.boardWidth,
5304     oldy = gameInfo.boardHeight,
5305     oldh = gameInfo.holdingsWidth,
5306     oldv = gameInfo.variant;
5307
5308     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5309
5310     /* [AS] Initialize pv info list [HGM] and game status */
5311     {
5312         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5313             pvInfoList[i].depth = 0;
5314             boards[i][EP_STATUS] = EP_NONE;
5315             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5316         }
5317
5318         initialRulePlies = 0; /* 50-move counter start */
5319
5320         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5321         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5322     }
5323
5324
5325     /* [HGM] logic here is completely changed. In stead of full positions */
5326     /* the initialized data only consist of the two backranks. The switch */
5327     /* selects which one we will use, which is than copied to the Board   */
5328     /* initialPosition, which for the rest is initialized by Pawns and    */
5329     /* empty squares. This initial position is then copied to boards[0],  */
5330     /* possibly after shuffling, so that it remains available.            */
5331
5332     gameInfo.holdingsWidth = 0; /* default board sizes */
5333     gameInfo.boardWidth    = 8;
5334     gameInfo.boardHeight   = 8;
5335     gameInfo.holdingsSize  = 0;
5336     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5337     for(i=0; i<BOARD_FILES-2; i++)
5338       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5339     initialPosition[EP_STATUS] = EP_NONE;
5340     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5341     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5342          SetCharTable(pieceNickName, appData.pieceNickNames);
5343     else SetCharTable(pieceNickName, "............");
5344     pieces = FIDEArray;
5345
5346     switch (gameInfo.variant) {
5347     case VariantFischeRandom:
5348       shuffleOpenings = TRUE;
5349     default:
5350       break;
5351     case VariantShatranj:
5352       pieces = ShatranjArray;
5353       nrCastlingRights = 0;
5354       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5355       break;
5356     case VariantMakruk:
5357       pieces = makrukArray;
5358       nrCastlingRights = 0;
5359       startedFromSetupPosition = TRUE;
5360       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5361       break;
5362     case VariantTwoKings:
5363       pieces = twoKingsArray;
5364       break;
5365     case VariantCapaRandom:
5366       shuffleOpenings = TRUE;
5367     case VariantCapablanca:
5368       pieces = CapablancaArray;
5369       gameInfo.boardWidth = 10;
5370       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5371       break;
5372     case VariantGothic:
5373       pieces = GothicArray;
5374       gameInfo.boardWidth = 10;
5375       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5376       break;
5377     case VariantSChess:
5378       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5379       gameInfo.holdingsSize = 7;
5380       break;
5381     case VariantJanus:
5382       pieces = JanusArray;
5383       gameInfo.boardWidth = 10;
5384       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5385       nrCastlingRights = 6;
5386         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5387         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5388         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5389         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5390         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5391         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5392       break;
5393     case VariantFalcon:
5394       pieces = FalconArray;
5395       gameInfo.boardWidth = 10;
5396       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5397       break;
5398     case VariantXiangqi:
5399       pieces = XiangqiArray;
5400       gameInfo.boardWidth  = 9;
5401       gameInfo.boardHeight = 10;
5402       nrCastlingRights = 0;
5403       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5404       break;
5405     case VariantShogi:
5406       pieces = ShogiArray;
5407       gameInfo.boardWidth  = 9;
5408       gameInfo.boardHeight = 9;
5409       gameInfo.holdingsSize = 7;
5410       nrCastlingRights = 0;
5411       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5412       break;
5413     case VariantCourier:
5414       pieces = CourierArray;
5415       gameInfo.boardWidth  = 12;
5416       nrCastlingRights = 0;
5417       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5418       break;
5419     case VariantKnightmate:
5420       pieces = KnightmateArray;
5421       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5422       break;
5423     case VariantFairy:
5424       pieces = fairyArray;
5425       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5426       break;
5427     case VariantGreat:
5428       pieces = GreatArray;
5429       gameInfo.boardWidth = 10;
5430       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5431       gameInfo.holdingsSize = 8;
5432       break;
5433     case VariantSuper:
5434       pieces = FIDEArray;
5435       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5436       gameInfo.holdingsSize = 8;
5437       startedFromSetupPosition = TRUE;
5438       break;
5439     case VariantCrazyhouse:
5440     case VariantBughouse:
5441       pieces = FIDEArray;
5442       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5443       gameInfo.holdingsSize = 5;
5444       break;
5445     case VariantWildCastle:
5446       pieces = FIDEArray;
5447       /* !!?shuffle with kings guaranteed to be on d or e file */
5448       shuffleOpenings = 1;
5449       break;
5450     case VariantNoCastle:
5451       pieces = FIDEArray;
5452       nrCastlingRights = 0;
5453       /* !!?unconstrained back-rank shuffle */
5454       shuffleOpenings = 1;
5455       break;
5456     }
5457
5458     overrule = 0;
5459     if(appData.NrFiles >= 0) {
5460         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5461         gameInfo.boardWidth = appData.NrFiles;
5462     }
5463     if(appData.NrRanks >= 0) {
5464         gameInfo.boardHeight = appData.NrRanks;
5465     }
5466     if(appData.holdingsSize >= 0) {
5467         i = appData.holdingsSize;
5468         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5469         gameInfo.holdingsSize = i;
5470     }
5471     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5472     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5473         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5474
5475     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5476     if(pawnRow < 1) pawnRow = 1;
5477     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5478
5479     /* User pieceToChar list overrules defaults */
5480     if(appData.pieceToCharTable != NULL)
5481         SetCharTable(pieceToChar, appData.pieceToCharTable);
5482
5483     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5484
5485         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5486             s = (ChessSquare) 0; /* account holding counts in guard band */
5487         for( i=0; i<BOARD_HEIGHT; i++ )
5488             initialPosition[i][j] = s;
5489
5490         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5491         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5492         initialPosition[pawnRow][j] = WhitePawn;
5493         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5494         if(gameInfo.variant == VariantXiangqi) {
5495             if(j&1) {
5496                 initialPosition[pawnRow][j] =
5497                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5498                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5499                    initialPosition[2][j] = WhiteCannon;
5500                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5501                 }
5502             }
5503         }
5504         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5505     }
5506     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5507
5508             j=BOARD_LEFT+1;
5509             initialPosition[1][j] = WhiteBishop;
5510             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5511             j=BOARD_RGHT-2;
5512             initialPosition[1][j] = WhiteRook;
5513             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5514     }
5515
5516     if( nrCastlingRights == -1) {
5517         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5518         /*       This sets default castling rights from none to normal corners   */
5519         /* Variants with other castling rights must set them themselves above    */
5520         nrCastlingRights = 6;
5521
5522         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5523         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5524         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5525         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5526         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5527         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5528      }
5529
5530      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5531      if(gameInfo.variant == VariantGreat) { // promotion commoners
5532         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5533         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5534         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5535         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5536      }
5537      if( gameInfo.variant == VariantSChess ) {
5538       initialPosition[1][0] = BlackMarshall;
5539       initialPosition[2][0] = BlackAngel;
5540       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5541       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5542       initialPosition[1][1] = initialPosition[2][1] = 
5543       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5544      }
5545   if (appData.debugMode) {
5546     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5547   }
5548     if(shuffleOpenings) {
5549         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5550         startedFromSetupPosition = TRUE;
5551     }
5552     if(startedFromPositionFile) {
5553       /* [HGM] loadPos: use PositionFile for every new game */
5554       CopyBoard(initialPosition, filePosition);
5555       for(i=0; i<nrCastlingRights; i++)
5556           initialRights[i] = filePosition[CASTLING][i];
5557       startedFromSetupPosition = TRUE;
5558     }
5559
5560     CopyBoard(boards[0], initialPosition);
5561
5562     if(oldx != gameInfo.boardWidth ||
5563        oldy != gameInfo.boardHeight ||
5564        oldh != gameInfo.holdingsWidth
5565 #ifdef GOTHIC
5566        || oldv == VariantGothic ||        // For licensing popups
5567        gameInfo.variant == VariantGothic
5568 #endif
5569 #ifdef FALCON
5570        || oldv == VariantFalcon ||
5571        gameInfo.variant == VariantFalcon
5572 #endif
5573                                          )
5574             InitDrawingSizes(-2 ,0);
5575
5576     if (redraw)
5577       DrawPosition(TRUE, boards[currentMove]);
5578 }
5579
5580 void
5581 SendBoard(cps, moveNum)
5582      ChessProgramState *cps;
5583      int moveNum;
5584 {
5585     char message[MSG_SIZ];
5586
5587     if (cps->useSetboard) {
5588       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5589       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5590       SendToProgram(message, cps);
5591       free(fen);
5592
5593     } else {
5594       ChessSquare *bp;
5595       int i, j;
5596       /* Kludge to set black to move, avoiding the troublesome and now
5597        * deprecated "black" command.
5598        */
5599       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5600         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5601
5602       SendToProgram("edit\n", cps);
5603       SendToProgram("#\n", cps);
5604       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5605         bp = &boards[moveNum][i][BOARD_LEFT];
5606         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5607           if ((int) *bp < (int) BlackPawn) {
5608             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5609                     AAA + j, ONE + i);
5610             if(message[0] == '+' || message[0] == '~') {
5611               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5612                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5613                         AAA + j, ONE + i);
5614             }
5615             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5616                 message[1] = BOARD_RGHT   - 1 - j + '1';
5617                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5618             }
5619             SendToProgram(message, cps);
5620           }
5621         }
5622       }
5623
5624       SendToProgram("c\n", cps);
5625       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5626         bp = &boards[moveNum][i][BOARD_LEFT];
5627         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5628           if (((int) *bp != (int) EmptySquare)
5629               && ((int) *bp >= (int) BlackPawn)) {
5630             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5631                     AAA + j, ONE + i);
5632             if(message[0] == '+' || message[0] == '~') {
5633               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5634                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5635                         AAA + j, ONE + i);
5636             }
5637             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5638                 message[1] = BOARD_RGHT   - 1 - j + '1';
5639                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5640             }
5641             SendToProgram(message, cps);
5642           }
5643         }
5644       }
5645
5646       SendToProgram(".\n", cps);
5647     }
5648     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5649 }
5650
5651 static int autoQueen; // [HGM] oneclick
5652
5653 int
5654 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5655 {
5656     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5657     /* [HGM] add Shogi promotions */
5658     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5659     ChessSquare piece;
5660     ChessMove moveType;
5661     Boolean premove;
5662
5663     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5664     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5665
5666     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5667       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5668         return FALSE;
5669
5670     piece = boards[currentMove][fromY][fromX];
5671     if(gameInfo.variant == VariantShogi) {
5672         promotionZoneSize = BOARD_HEIGHT/3;
5673         highestPromotingPiece = (int)WhiteFerz;
5674     } else if(gameInfo.variant == VariantMakruk) {
5675         promotionZoneSize = 3;
5676     }
5677
5678     // next weed out all moves that do not touch the promotion zone at all
5679     if((int)piece >= BlackPawn) {
5680         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5681              return FALSE;
5682         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5683     } else {
5684         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5685            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5686     }
5687
5688     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5689
5690     // weed out mandatory Shogi promotions
5691     if(gameInfo.variant == VariantShogi) {
5692         if(piece >= BlackPawn) {
5693             if(toY == 0 && piece == BlackPawn ||
5694                toY == 0 && piece == BlackQueen ||
5695                toY <= 1 && piece == BlackKnight) {
5696                 *promoChoice = '+';
5697                 return FALSE;
5698             }
5699         } else {
5700             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5701                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5702                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5703                 *promoChoice = '+';
5704                 return FALSE;
5705             }
5706         }
5707     }
5708
5709     // weed out obviously illegal Pawn moves
5710     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5711         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5712         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5713         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5714         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5715         // note we are not allowed to test for valid (non-)capture, due to premove
5716     }
5717
5718     // we either have a choice what to promote to, or (in Shogi) whether to promote
5719     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5720         *promoChoice = PieceToChar(BlackFerz);  // no choice
5721         return FALSE;
5722     }
5723     // no sense asking what we must promote to if it is going to explode...
5724     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5725         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5726         return FALSE;
5727     }
5728     if(autoQueen) { // predetermined
5729         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5730              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5731         else *promoChoice = PieceToChar(BlackQueen);
5732         return FALSE;
5733     }
5734
5735     // suppress promotion popup on illegal moves that are not premoves
5736     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5737               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5738     if(appData.testLegality && !premove) {
5739         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5740                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5741         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5742             return FALSE;
5743     }
5744
5745     return TRUE;
5746 }
5747
5748 int
5749 InPalace(row, column)
5750      int row, column;
5751 {   /* [HGM] for Xiangqi */
5752     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5753          column < (BOARD_WIDTH + 4)/2 &&
5754          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5755     return FALSE;
5756 }
5757
5758 int
5759 PieceForSquare (x, y)
5760      int x;
5761      int y;
5762 {
5763   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5764      return -1;
5765   else
5766      return boards[currentMove][y][x];
5767 }
5768
5769 int
5770 OKToStartUserMove(x, y)
5771      int x, y;
5772 {
5773     ChessSquare from_piece;
5774     int white_piece;
5775
5776     if (matchMode) return FALSE;
5777     if (gameMode == EditPosition) return TRUE;
5778
5779     if (x >= 0 && y >= 0)
5780       from_piece = boards[currentMove][y][x];
5781     else
5782       from_piece = EmptySquare;
5783
5784     if (from_piece == EmptySquare) return FALSE;
5785
5786     white_piece = (int)from_piece >= (int)WhitePawn &&
5787       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5788
5789     switch (gameMode) {
5790       case PlayFromGameFile:
5791       case AnalyzeFile:
5792       case TwoMachinesPlay:
5793       case EndOfGame:
5794         return FALSE;
5795
5796       case IcsObserving:
5797       case IcsIdle:
5798         return FALSE;
5799
5800       case MachinePlaysWhite:
5801       case IcsPlayingBlack:
5802         if (appData.zippyPlay) return FALSE;
5803         if (white_piece) {
5804             DisplayMoveError(_("You are playing Black"));
5805             return FALSE;
5806         }
5807         break;
5808
5809       case MachinePlaysBlack:
5810       case IcsPlayingWhite:
5811         if (appData.zippyPlay) return FALSE;
5812         if (!white_piece) {
5813             DisplayMoveError(_("You are playing White"));
5814             return FALSE;
5815         }
5816         break;
5817
5818       case EditGame:
5819         if (!white_piece && WhiteOnMove(currentMove)) {
5820             DisplayMoveError(_("It is White's turn"));
5821             return FALSE;
5822         }
5823         if (white_piece && !WhiteOnMove(currentMove)) {
5824             DisplayMoveError(_("It is Black's turn"));
5825             return FALSE;
5826         }
5827         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5828             /* Editing correspondence game history */
5829             /* Could disallow this or prompt for confirmation */
5830             cmailOldMove = -1;
5831         }
5832         break;
5833
5834       case BeginningOfGame:
5835         if (appData.icsActive) return FALSE;
5836         if (!appData.noChessProgram) {
5837             if (!white_piece) {
5838                 DisplayMoveError(_("You are playing White"));
5839                 return FALSE;
5840             }
5841         }
5842         break;
5843
5844       case Training:
5845         if (!white_piece && WhiteOnMove(currentMove)) {
5846             DisplayMoveError(_("It is White's turn"));
5847             return FALSE;
5848         }
5849         if (white_piece && !WhiteOnMove(currentMove)) {
5850             DisplayMoveError(_("It is Black's turn"));
5851             return FALSE;
5852         }
5853         break;
5854
5855       default:
5856       case IcsExamining:
5857         break;
5858     }
5859     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5860         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5861         && gameMode != AnalyzeFile && gameMode != Training) {
5862         DisplayMoveError(_("Displayed position is not current"));
5863         return FALSE;
5864     }
5865     return TRUE;
5866 }
5867
5868 Boolean
5869 OnlyMove(int *x, int *y, Boolean captures) {
5870     DisambiguateClosure cl;
5871     if (appData.zippyPlay) return FALSE;
5872     switch(gameMode) {
5873       case MachinePlaysBlack:
5874       case IcsPlayingWhite:
5875       case BeginningOfGame:
5876         if(!WhiteOnMove(currentMove)) return FALSE;
5877         break;
5878       case MachinePlaysWhite:
5879       case IcsPlayingBlack:
5880         if(WhiteOnMove(currentMove)) return FALSE;
5881         break;
5882       case EditGame:
5883         break;
5884       default:
5885         return FALSE;
5886     }
5887     cl.pieceIn = EmptySquare;
5888     cl.rfIn = *y;
5889     cl.ffIn = *x;
5890     cl.rtIn = -1;
5891     cl.ftIn = -1;
5892     cl.promoCharIn = NULLCHAR;
5893     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5894     if( cl.kind == NormalMove ||
5895         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5896         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5897         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5898       fromX = cl.ff;
5899       fromY = cl.rf;
5900       *x = cl.ft;
5901       *y = cl.rt;
5902       return TRUE;
5903     }
5904     if(cl.kind != ImpossibleMove) return FALSE;
5905     cl.pieceIn = EmptySquare;
5906     cl.rfIn = -1;
5907     cl.ffIn = -1;
5908     cl.rtIn = *y;
5909     cl.ftIn = *x;
5910     cl.promoCharIn = NULLCHAR;
5911     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5912     if( cl.kind == NormalMove ||
5913         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5914         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5915         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5916       fromX = cl.ff;
5917       fromY = cl.rf;
5918       *x = cl.ft;
5919       *y = cl.rt;
5920       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5921       return TRUE;
5922     }
5923     return FALSE;
5924 }
5925
5926 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5927 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5928 int lastLoadGameUseList = FALSE;
5929 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5930 ChessMove lastLoadGameStart = EndOfFile;
5931
5932 void
5933 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5934      int fromX, fromY, toX, toY;
5935      int promoChar;
5936 {
5937     ChessMove moveType;
5938     ChessSquare pdown, pup;
5939
5940     /* Check if the user is playing in turn.  This is complicated because we
5941        let the user "pick up" a piece before it is his turn.  So the piece he
5942        tried to pick up may have been captured by the time he puts it down!
5943        Therefore we use the color the user is supposed to be playing in this
5944        test, not the color of the piece that is currently on the starting
5945        square---except in EditGame mode, where the user is playing both
5946        sides; fortunately there the capture race can't happen.  (It can
5947        now happen in IcsExamining mode, but that's just too bad.  The user
5948        will get a somewhat confusing message in that case.)
5949        */
5950
5951     switch (gameMode) {
5952       case PlayFromGameFile:
5953       case AnalyzeFile:
5954       case TwoMachinesPlay:
5955       case EndOfGame:
5956       case IcsObserving:
5957       case IcsIdle:
5958         /* We switched into a game mode where moves are not accepted,
5959            perhaps while the mouse button was down. */
5960         return;
5961
5962       case MachinePlaysWhite:
5963         /* User is moving for Black */
5964         if (WhiteOnMove(currentMove)) {
5965             DisplayMoveError(_("It is White's turn"));
5966             return;
5967         }
5968         break;
5969
5970       case MachinePlaysBlack:
5971         /* User is moving for White */
5972         if (!WhiteOnMove(currentMove)) {
5973             DisplayMoveError(_("It is Black's turn"));
5974             return;
5975         }
5976         break;
5977
5978       case EditGame:
5979       case IcsExamining:
5980       case BeginningOfGame:
5981       case AnalyzeMode:
5982       case Training:
5983         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5984             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5985             /* User is moving for Black */
5986             if (WhiteOnMove(currentMove)) {
5987                 DisplayMoveError(_("It is White's turn"));
5988                 return;
5989             }
5990         } else {
5991             /* User is moving for White */
5992             if (!WhiteOnMove(currentMove)) {
5993                 DisplayMoveError(_("It is Black's turn"));
5994                 return;
5995             }
5996         }
5997         break;
5998
5999       case IcsPlayingBlack:
6000         /* User is moving for Black */
6001         if (WhiteOnMove(currentMove)) {
6002             if (!appData.premove) {
6003                 DisplayMoveError(_("It is White's turn"));
6004             } else if (toX >= 0 && toY >= 0) {
6005                 premoveToX = toX;
6006                 premoveToY = toY;
6007                 premoveFromX = fromX;
6008                 premoveFromY = fromY;
6009                 premovePromoChar = promoChar;
6010                 gotPremove = 1;
6011                 if (appData.debugMode)
6012                     fprintf(debugFP, "Got premove: fromX %d,"
6013                             "fromY %d, toX %d, toY %d\n",
6014                             fromX, fromY, toX, toY);
6015             }
6016             return;
6017         }
6018         break;
6019
6020       case IcsPlayingWhite:
6021         /* User is moving for White */
6022         if (!WhiteOnMove(currentMove)) {
6023             if (!appData.premove) {
6024                 DisplayMoveError(_("It is Black's turn"));
6025             } else if (toX >= 0 && toY >= 0) {
6026                 premoveToX = toX;
6027                 premoveToY = toY;
6028                 premoveFromX = fromX;
6029                 premoveFromY = fromY;
6030                 premovePromoChar = promoChar;
6031                 gotPremove = 1;
6032                 if (appData.debugMode)
6033                     fprintf(debugFP, "Got premove: fromX %d,"
6034                             "fromY %d, toX %d, toY %d\n",
6035                             fromX, fromY, toX, toY);
6036             }
6037             return;
6038         }
6039         break;
6040
6041       default:
6042         break;
6043
6044       case EditPosition:
6045         /* EditPosition, empty square, or different color piece;
6046            click-click move is possible */
6047         if (toX == -2 || toY == -2) {
6048             boards[0][fromY][fromX] = EmptySquare;
6049             DrawPosition(FALSE, boards[currentMove]);
6050             return;
6051         } else if (toX >= 0 && toY >= 0) {
6052             boards[0][toY][toX] = boards[0][fromY][fromX];
6053             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6054                 if(boards[0][fromY][0] != EmptySquare) {
6055                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6056                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6057                 }
6058             } else
6059             if(fromX == BOARD_RGHT+1) {
6060                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6061                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6062                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6063                 }
6064             } else
6065             boards[0][fromY][fromX] = EmptySquare;
6066             DrawPosition(FALSE, boards[currentMove]);
6067             return;
6068         }
6069         return;
6070     }
6071
6072     if(toX < 0 || toY < 0) return;
6073     pdown = boards[currentMove][fromY][fromX];
6074     pup = boards[currentMove][toY][toX];
6075
6076     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6077     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) { 
6078          if( pup != EmptySquare ) return;
6079          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6080            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6081                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6082            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6083            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6084            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6085            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6086          fromY = DROP_RANK;
6087     }
6088
6089     /* [HGM] always test for legality, to get promotion info */
6090     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6091                                          fromY, fromX, toY, toX, promoChar);
6092     /* [HGM] but possibly ignore an IllegalMove result */
6093     if (appData.testLegality) {
6094         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6095             DisplayMoveError(_("Illegal move"));
6096             return;
6097         }
6098     }
6099
6100     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6101 }
6102
6103 /* Common tail of UserMoveEvent and DropMenuEvent */
6104 int
6105 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6106      ChessMove moveType;
6107      int fromX, fromY, toX, toY;
6108      /*char*/int promoChar;
6109 {
6110     char *bookHit = 0;
6111
6112     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6113         // [HGM] superchess: suppress promotions to non-available piece
6114         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6115         if(WhiteOnMove(currentMove)) {
6116             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6117         } else {
6118             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6119         }
6120     }
6121
6122     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6123        move type in caller when we know the move is a legal promotion */
6124     if(moveType == NormalMove && promoChar)
6125         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6126
6127     /* [HGM] <popupFix> The following if has been moved here from
6128        UserMoveEvent(). Because it seemed to belong here (why not allow
6129        piece drops in training games?), and because it can only be
6130        performed after it is known to what we promote. */
6131     if (gameMode == Training) {
6132       /* compare the move played on the board to the next move in the
6133        * game. If they match, display the move and the opponent's response.
6134        * If they don't match, display an error message.
6135        */
6136       int saveAnimate;
6137       Board testBoard;
6138       CopyBoard(testBoard, boards[currentMove]);
6139       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6140
6141       if (CompareBoards(testBoard, boards[currentMove+1])) {
6142         ForwardInner(currentMove+1);
6143
6144         /* Autoplay the opponent's response.
6145          * if appData.animate was TRUE when Training mode was entered,
6146          * the response will be animated.
6147          */
6148         saveAnimate = appData.animate;
6149         appData.animate = animateTraining;
6150         ForwardInner(currentMove+1);
6151         appData.animate = saveAnimate;
6152
6153         /* check for the end of the game */
6154         if (currentMove >= forwardMostMove) {
6155           gameMode = PlayFromGameFile;
6156           ModeHighlight();
6157           SetTrainingModeOff();
6158           DisplayInformation(_("End of game"));
6159         }
6160       } else {
6161         DisplayError(_("Incorrect move"), 0);
6162       }
6163       return 1;
6164     }
6165
6166   /* Ok, now we know that the move is good, so we can kill
6167      the previous line in Analysis Mode */
6168   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6169                                 && currentMove < forwardMostMove) {
6170     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6171     else forwardMostMove = currentMove;
6172   }
6173
6174   /* If we need the chess program but it's dead, restart it */
6175   ResurrectChessProgram();
6176
6177   /* A user move restarts a paused game*/
6178   if (pausing)
6179     PauseEvent();
6180
6181   thinkOutput[0] = NULLCHAR;
6182
6183   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6184
6185   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6186
6187   if (gameMode == BeginningOfGame) {
6188     if (appData.noChessProgram) {
6189       gameMode = EditGame;
6190       SetGameInfo();
6191     } else {
6192       char buf[MSG_SIZ];
6193       gameMode = MachinePlaysBlack;
6194       StartClocks();
6195       SetGameInfo();
6196       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6197       DisplayTitle(buf);
6198       if (first.sendName) {
6199         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6200         SendToProgram(buf, &first);
6201       }
6202       StartClocks();
6203     }
6204     ModeHighlight();
6205   }
6206
6207   /* Relay move to ICS or chess engine */
6208   if (appData.icsActive) {
6209     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6210         gameMode == IcsExamining) {
6211       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6212         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6213         SendToICS("draw ");
6214         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6215       }
6216       // also send plain move, in case ICS does not understand atomic claims
6217       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6218       ics_user_moved = 1;
6219     }
6220   } else {
6221     if (first.sendTime && (gameMode == BeginningOfGame ||
6222                            gameMode == MachinePlaysWhite ||
6223                            gameMode == MachinePlaysBlack)) {
6224       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6225     }
6226     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6227          // [HGM] book: if program might be playing, let it use book
6228         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6229         first.maybeThinking = TRUE;
6230     } else SendMoveToProgram(forwardMostMove-1, &first);
6231     if (currentMove == cmailOldMove + 1) {
6232       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6233     }
6234   }
6235
6236   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6237
6238   switch (gameMode) {
6239   case EditGame:
6240     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6241     case MT_NONE:
6242     case MT_CHECK:
6243       break;
6244     case MT_CHECKMATE:
6245     case MT_STAINMATE:
6246       if (WhiteOnMove(currentMove)) {
6247         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6248       } else {
6249         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6250       }
6251       break;
6252     case MT_STALEMATE:
6253       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6254       break;
6255     }
6256     break;
6257
6258   case MachinePlaysBlack:
6259   case MachinePlaysWhite:
6260     /* disable certain menu options while machine is thinking */
6261     SetMachineThinkingEnables();
6262     break;
6263
6264   default:
6265     break;
6266   }
6267
6268   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6269
6270   if(bookHit) { // [HGM] book: simulate book reply
6271         static char bookMove[MSG_SIZ]; // a bit generous?
6272
6273         programStats.nodes = programStats.depth = programStats.time =
6274         programStats.score = programStats.got_only_move = 0;
6275         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6276
6277         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6278         strcat(bookMove, bookHit);
6279         HandleMachineMove(bookMove, &first);
6280   }
6281   return 1;
6282 }
6283
6284 void
6285 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6286      Board board;
6287      int flags;
6288      ChessMove kind;
6289      int rf, ff, rt, ft;
6290      VOIDSTAR closure;
6291 {
6292     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6293     Markers *m = (Markers *) closure;
6294     if(rf == fromY && ff == fromX)
6295         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6296                          || kind == WhiteCapturesEnPassant
6297                          || kind == BlackCapturesEnPassant);
6298     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6299 }
6300
6301 void
6302 MarkTargetSquares(int clear)
6303 {
6304   int x, y;
6305   if(!appData.markers || !appData.highlightDragging ||
6306      !appData.testLegality || gameMode == EditPosition) return;
6307   if(clear) {
6308     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6309   } else {
6310     int capt = 0;
6311     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6312     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6313       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6314       if(capt)
6315       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6316     }
6317   }
6318   DrawPosition(TRUE, NULL);
6319 }
6320
6321 int
6322 Explode(Board board, int fromX, int fromY, int toX, int toY)
6323 {
6324     if(gameInfo.variant == VariantAtomic &&
6325        (board[toY][toX] != EmptySquare ||                     // capture?
6326         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6327                          board[fromY][fromX] == BlackPawn   )
6328       )) {
6329         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6330         return TRUE;
6331     }
6332     return FALSE;
6333 }
6334
6335 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6336
6337 void LeftClick(ClickType clickType, int xPix, int yPix)
6338 {
6339     int x, y;
6340     Boolean saveAnimate;
6341     static int second = 0, promotionChoice = 0, dragging = 0;
6342     char promoChoice = NULLCHAR;
6343
6344     if(appData.seekGraph && appData.icsActive && loggedOn &&
6345         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6346         SeekGraphClick(clickType, xPix, yPix, 0);
6347         return;
6348     }
6349
6350     if (clickType == Press) ErrorPopDown();
6351     MarkTargetSquares(1);
6352
6353     x = EventToSquare(xPix, BOARD_WIDTH);
6354     y = EventToSquare(yPix, BOARD_HEIGHT);
6355     if (!flipView && y >= 0) {
6356         y = BOARD_HEIGHT - 1 - y;
6357     }
6358     if (flipView && x >= 0) {
6359         x = BOARD_WIDTH - 1 - x;
6360     }
6361
6362     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6363         if(clickType == Release) return; // ignore upclick of click-click destination
6364         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6365         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6366         if(gameInfo.holdingsWidth &&
6367                 (WhiteOnMove(currentMove)
6368                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6369                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6370             // click in right holdings, for determining promotion piece
6371             ChessSquare p = boards[currentMove][y][x];
6372             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6373             if(p != EmptySquare) {
6374                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6375                 fromX = fromY = -1;
6376                 return;
6377             }
6378         }
6379         DrawPosition(FALSE, boards[currentMove]);
6380         return;
6381     }
6382
6383     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6384     if(clickType == Press
6385             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6386               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6387               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6388         return;
6389
6390     autoQueen = appData.alwaysPromoteToQueen;
6391
6392     if (fromX == -1) {
6393       gatingPiece = EmptySquare;
6394       if (clickType != Press) {
6395         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6396             DragPieceEnd(xPix, yPix); dragging = 0;
6397             DrawPosition(FALSE, NULL);
6398         }
6399         return;
6400       }
6401       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6402             /* First square */
6403             if (OKToStartUserMove(x, y)) {
6404                 fromX = x;
6405                 fromY = y;
6406                 second = 0;
6407                 MarkTargetSquares(0);
6408                 DragPieceBegin(xPix, yPix); dragging = 1;
6409                 if (appData.highlightDragging) {
6410                     SetHighlights(x, y, -1, -1);
6411                 }
6412             }
6413             return;
6414         }
6415     }
6416
6417     /* fromX != -1 */
6418     if (clickType == Press && gameMode != EditPosition) {
6419         ChessSquare fromP;
6420         ChessSquare toP;
6421         int frc;
6422
6423         // ignore off-board to clicks
6424         if(y < 0 || x < 0) return;
6425
6426         /* Check if clicking again on the same color piece */
6427         fromP = boards[currentMove][fromY][fromX];
6428         toP = boards[currentMove][y][x];
6429         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6430         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6431              WhitePawn <= toP && toP <= WhiteKing &&
6432              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6433              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6434             (BlackPawn <= fromP && fromP <= BlackKing &&
6435              BlackPawn <= toP && toP <= BlackKing &&
6436              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6437              !(fromP == BlackKing && toP == BlackRook && frc))) {
6438             /* Clicked again on same color piece -- changed his mind */
6439             second = (x == fromX && y == fromY);
6440            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6441             if (appData.highlightDragging) {
6442                 SetHighlights(x, y, -1, -1);
6443             } else {
6444                 ClearHighlights();
6445             }
6446             if (OKToStartUserMove(x, y)) {
6447                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6448                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6449                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6450                  gatingPiece = boards[currentMove][fromY][fromX];
6451                 else gatingPiece = EmptySquare;
6452                 fromX = x;
6453                 fromY = y; dragging = 1;
6454                 MarkTargetSquares(0);
6455                 DragPieceBegin(xPix, yPix);
6456             }
6457            }
6458            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6459            second = FALSE; 
6460         }
6461         // ignore clicks on holdings
6462         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6463     }
6464
6465     if (clickType == Release && x == fromX && y == fromY) {
6466         DragPieceEnd(xPix, yPix); dragging = 0;
6467         if (appData.animateDragging) {
6468             /* Undo animation damage if any */
6469             DrawPosition(FALSE, NULL);
6470         }
6471         if (second) {
6472             /* Second up/down in same square; just abort move */
6473             second = 0;
6474             fromX = fromY = -1;
6475             gatingPiece = EmptySquare;
6476             ClearHighlights();
6477             gotPremove = 0;
6478             ClearPremoveHighlights();
6479         } else {
6480             /* First upclick in same square; start click-click mode */
6481             SetHighlights(x, y, -1, -1);
6482         }
6483         return;
6484     }
6485
6486     /* we now have a different from- and (possibly off-board) to-square */
6487     /* Completed move */
6488     toX = x;
6489     toY = y;
6490     saveAnimate = appData.animate;
6491     if (clickType == Press) {
6492         /* Finish clickclick move */
6493         if (appData.animate || appData.highlightLastMove) {
6494             SetHighlights(fromX, fromY, toX, toY);
6495         } else {
6496             ClearHighlights();
6497         }
6498     } else {
6499         /* Finish drag move */
6500         if (appData.highlightLastMove) {
6501             SetHighlights(fromX, fromY, toX, toY);
6502         } else {
6503             ClearHighlights();
6504         }
6505         DragPieceEnd(xPix, yPix); dragging = 0;
6506         /* Don't animate move and drag both */
6507         appData.animate = FALSE;
6508     }
6509
6510     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6511     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6512         ChessSquare piece = boards[currentMove][fromY][fromX];
6513         if(gameMode == EditPosition && piece != EmptySquare &&
6514            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6515             int n;
6516
6517             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6518                 n = PieceToNumber(piece - (int)BlackPawn);
6519                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6520                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6521                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6522             } else
6523             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6524                 n = PieceToNumber(piece);
6525                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6526                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6527                 boards[currentMove][n][BOARD_WIDTH-2]++;
6528             }
6529             boards[currentMove][fromY][fromX] = EmptySquare;
6530         }
6531         ClearHighlights();
6532         fromX = fromY = -1;
6533         DrawPosition(TRUE, boards[currentMove]);
6534         return;
6535     }
6536
6537     // off-board moves should not be highlighted
6538     if(x < 0 || y < 0) ClearHighlights();
6539
6540     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6541
6542     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6543         SetHighlights(fromX, fromY, toX, toY);
6544         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6545             // [HGM] super: promotion to captured piece selected from holdings
6546             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6547             promotionChoice = TRUE;
6548             // kludge follows to temporarily execute move on display, without promoting yet
6549             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6550             boards[currentMove][toY][toX] = p;
6551             DrawPosition(FALSE, boards[currentMove]);
6552             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6553             boards[currentMove][toY][toX] = q;
6554             DisplayMessage("Click in holdings to choose piece", "");
6555             return;
6556         }
6557         PromotionPopUp();
6558     } else {
6559         int oldMove = currentMove;
6560         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6561         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6562         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6563         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6564            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6565             DrawPosition(TRUE, boards[currentMove]);
6566         fromX = fromY = -1;
6567     }
6568     appData.animate = saveAnimate;
6569     if (appData.animate || appData.animateDragging) {
6570         /* Undo animation damage if needed */
6571         DrawPosition(FALSE, NULL);
6572     }
6573 }
6574
6575 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6576 {   // front-end-free part taken out of PieceMenuPopup
6577     int whichMenu; int xSqr, ySqr;
6578
6579     if(seekGraphUp) { // [HGM] seekgraph
6580         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6581         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6582         return -2;
6583     }
6584
6585     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6586          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6587         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6588         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6589         if(action == Press)   {
6590             originalFlip = flipView;
6591             flipView = !flipView; // temporarily flip board to see game from partners perspective
6592             DrawPosition(TRUE, partnerBoard);
6593             DisplayMessage(partnerStatus, "");
6594             partnerUp = TRUE;
6595         } else if(action == Release) {
6596             flipView = originalFlip;
6597             DrawPosition(TRUE, boards[currentMove]);
6598             partnerUp = FALSE;
6599         }
6600         return -2;
6601     }
6602
6603     xSqr = EventToSquare(x, BOARD_WIDTH);
6604     ySqr = EventToSquare(y, BOARD_HEIGHT);
6605     if (action == Release) UnLoadPV(); // [HGM] pv
6606     if (action != Press) return -2; // return code to be ignored
6607     switch (gameMode) {
6608       case IcsExamining:
6609         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6610       case EditPosition:
6611         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6612         if (xSqr < 0 || ySqr < 0) return -1;\r
6613         whichMenu = 0; // edit-position menu
6614         break;
6615       case IcsObserving:
6616         if(!appData.icsEngineAnalyze) return -1;
6617       case IcsPlayingWhite:
6618       case IcsPlayingBlack:
6619         if(!appData.zippyPlay) goto noZip;
6620       case AnalyzeMode:
6621       case AnalyzeFile:
6622       case MachinePlaysWhite:
6623       case MachinePlaysBlack:
6624       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6625         if (!appData.dropMenu) {
6626           LoadPV(x, y);
6627           return 2; // flag front-end to grab mouse events
6628         }
6629         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6630            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6631       case EditGame:
6632       noZip:
6633         if (xSqr < 0 || ySqr < 0) return -1;
6634         if (!appData.dropMenu || appData.testLegality &&
6635             gameInfo.variant != VariantBughouse &&
6636             gameInfo.variant != VariantCrazyhouse) return -1;
6637         whichMenu = 1; // drop menu
6638         break;
6639       default:
6640         return -1;
6641     }
6642
6643     if (((*fromX = xSqr) < 0) ||
6644         ((*fromY = ySqr) < 0)) {
6645         *fromX = *fromY = -1;
6646         return -1;
6647     }
6648     if (flipView)
6649       *fromX = BOARD_WIDTH - 1 - *fromX;
6650     else
6651       *fromY = BOARD_HEIGHT - 1 - *fromY;
6652
6653     return whichMenu;
6654 }
6655
6656 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6657 {
6658 //    char * hint = lastHint;
6659     FrontEndProgramStats stats;
6660
6661     stats.which = cps == &first ? 0 : 1;
6662     stats.depth = cpstats->depth;
6663     stats.nodes = cpstats->nodes;
6664     stats.score = cpstats->score;
6665     stats.time = cpstats->time;
6666     stats.pv = cpstats->movelist;
6667     stats.hint = lastHint;
6668     stats.an_move_index = 0;
6669     stats.an_move_count = 0;
6670
6671     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6672         stats.hint = cpstats->move_name;
6673         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6674         stats.an_move_count = cpstats->nr_moves;
6675     }
6676
6677     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
6678
6679     SetProgramStats( &stats );
6680 }
6681
6682 void
6683 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6684 {       // count all piece types
6685         int p, f, r;
6686         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6687         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6688         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6689                 p = board[r][f];
6690                 pCnt[p]++;
6691                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6692                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6693                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6694                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6695                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6696                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6697         }
6698 }
6699
6700 int
6701 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6702 {
6703         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6704         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6705
6706         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6707         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6708         if(myPawns == 2 && nMine == 3) // KPP
6709             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6710         if(myPawns == 1 && nMine == 2) // KP
6711             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6712         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6713             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6714         if(myPawns) return FALSE;
6715         if(pCnt[WhiteRook+side])
6716             return pCnt[BlackRook-side] ||
6717                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6718                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6719                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6720         if(pCnt[WhiteCannon+side]) {
6721             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6722             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6723         }
6724         if(pCnt[WhiteKnight+side])
6725             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6726         return FALSE;
6727 }
6728
6729 int
6730 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6731 {
6732         VariantClass v = gameInfo.variant;
6733
6734         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6735         if(v == VariantShatranj) return TRUE; // always winnable through baring
6736         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6737         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6738
6739         if(v == VariantXiangqi) {
6740                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6741
6742                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6743                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6744                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6745                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6746                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6747                 if(stale) // we have at least one last-rank P plus perhaps C
6748                     return majors // KPKX
6749                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6750                 else // KCA*E*
6751                     return pCnt[WhiteFerz+side] // KCAK
6752                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6753                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6754                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6755
6756         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6757                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6758
6759                 if(nMine == 1) return FALSE; // bare King
6760                 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
6761                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6762                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6763                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6764                 if(pCnt[WhiteKnight+side])
6765                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6766                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6767                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6768                 if(nBishops)
6769                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6770                 if(pCnt[WhiteAlfil+side])
6771                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6772                 if(pCnt[WhiteWazir+side])
6773                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6774         }
6775
6776         return TRUE;
6777 }
6778
6779 int
6780 Adjudicate(ChessProgramState *cps)
6781 {       // [HGM] some adjudications useful with buggy engines
6782         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6783         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6784         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6785         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6786         int k, count = 0; static int bare = 1;
6787         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6788         Boolean canAdjudicate = !appData.icsActive;
6789
6790         // most tests only when we understand the game, i.e. legality-checking on
6791             if( appData.testLegality )
6792             {   /* [HGM] Some more adjudications for obstinate engines */
6793                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6794                 static int moveCount = 6;
6795                 ChessMove result;
6796                 char *reason = NULL;
6797
6798                 /* Count what is on board. */
6799                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6800
6801                 /* Some material-based adjudications that have to be made before stalemate test */
6802                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6803                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6804                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6805                      if(canAdjudicate && appData.checkMates) {
6806                          if(engineOpponent)
6807                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6808                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6809                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6810                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6811                          return 1;
6812                      }
6813                 }
6814
6815                 /* Bare King in Shatranj (loses) or Losers (wins) */
6816                 if( nrW == 1 || nrB == 1) {
6817                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6818                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6819                      if(canAdjudicate && appData.checkMates) {
6820                          if(engineOpponent)
6821                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6822                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6823                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6824                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6825                          return 1;
6826                      }
6827                   } else
6828                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6829                   {    /* bare King */
6830                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6831                         if(canAdjudicate && appData.checkMates) {
6832                             /* but only adjudicate if adjudication enabled */
6833                             if(engineOpponent)
6834                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6835                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6836                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6837                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6838                             return 1;
6839                         }
6840                   }
6841                 } else bare = 1;
6842
6843
6844             // don't wait for engine to announce game end if we can judge ourselves
6845             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6846               case MT_CHECK:
6847                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6848                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6849                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6850                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6851                             checkCnt++;
6852                         if(checkCnt >= 2) {
6853                             reason = "Xboard adjudication: 3rd check";
6854                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6855                             break;
6856                         }
6857                     }
6858                 }
6859               case MT_NONE:
6860               default:
6861                 break;
6862               case MT_STALEMATE:
6863               case MT_STAINMATE:
6864                 reason = "Xboard adjudication: Stalemate";
6865                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6866                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6867                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6868                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6869                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6870                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6871                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6872                                                                         EP_CHECKMATE : EP_WINS);
6873                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6874                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6875                 }
6876                 break;
6877               case MT_CHECKMATE:
6878                 reason = "Xboard adjudication: Checkmate";
6879                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6880                 break;
6881             }
6882
6883                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6884                     case EP_STALEMATE:
6885                         result = GameIsDrawn; break;
6886                     case EP_CHECKMATE:
6887                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6888                     case EP_WINS:
6889                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6890                     default:
6891                         result = EndOfFile;
6892                 }
6893                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6894                     if(engineOpponent)
6895                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6896                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6897                     GameEnds( result, reason, GE_XBOARD );
6898                     return 1;
6899                 }
6900
6901                 /* Next absolutely insufficient mating material. */
6902                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6903                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6904                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6905
6906                      /* always flag draws, for judging claims */
6907                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6908
6909                      if(canAdjudicate && appData.materialDraws) {
6910                          /* but only adjudicate them if adjudication enabled */
6911                          if(engineOpponent) {
6912                            SendToProgram("force\n", engineOpponent); // suppress reply
6913                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6914                          }
6915                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6916                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6917                          return 1;
6918                      }
6919                 }
6920
6921                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6922                 if(gameInfo.variant == VariantXiangqi ?
6923                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6924                  : nrW + nrB == 4 &&
6925                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6926                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6927                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6928                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6929                    ) ) {
6930                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6931                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6932                           if(engineOpponent) {
6933                             SendToProgram("force\n", engineOpponent); // suppress reply
6934                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6935                           }
6936                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6937                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6938                           return 1;
6939                      }
6940                 } else moveCount = 6;
6941             }
6942         if (appData.debugMode) { int i;
6943             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6944                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6945                     appData.drawRepeats);
6946             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6947               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6948
6949         }
6950
6951         // Repetition draws and 50-move rule can be applied independently of legality testing
6952
6953                 /* Check for rep-draws */
6954                 count = 0;
6955                 for(k = forwardMostMove-2;
6956                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6957                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6958                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6959                     k-=2)
6960                 {   int rights=0;
6961                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6962                         /* compare castling rights */
6963                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6964                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6965                                 rights++; /* King lost rights, while rook still had them */
6966                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6967                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6968                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6969                                    rights++; /* but at least one rook lost them */
6970                         }
6971                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6972                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6973                                 rights++;
6974                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6975                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6976                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6977                                    rights++;
6978                         }
6979                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6980                             && appData.drawRepeats > 1) {
6981                              /* adjudicate after user-specified nr of repeats */
6982                              int result = GameIsDrawn;
6983                              char *details = "XBoard adjudication: repetition draw";
6984                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6985                                 // [HGM] xiangqi: check for forbidden perpetuals
6986                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6987                                 for(m=forwardMostMove; m>k; m-=2) {
6988                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6989                                         ourPerpetual = 0; // the current mover did not always check
6990                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6991                                         hisPerpetual = 0; // the opponent did not always check
6992                                 }
6993                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6994                                                                         ourPerpetual, hisPerpetual);
6995                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6996                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6997                                     details = "Xboard adjudication: perpetual checking";
6998                                 } else
6999                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7000                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7001                                 } else
7002                                 // Now check for perpetual chases
7003                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7004                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7005                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7006                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7007                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7008                                         details = "Xboard adjudication: perpetual chasing";
7009                                     } else
7010                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7011                                         break; // Abort repetition-checking loop.
7012                                 }
7013                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7014                              }
7015                              if(engineOpponent) {
7016                                SendToProgram("force\n", engineOpponent); // suppress reply
7017                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7018                              }
7019                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7020                              GameEnds( result, details, GE_XBOARD );
7021                              return 1;
7022                         }
7023                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7024                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7025                     }
7026                 }
7027
7028                 /* Now we test for 50-move draws. Determine ply count */
7029                 count = forwardMostMove;
7030                 /* look for last irreversble move */
7031                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7032                     count--;
7033                 /* if we hit starting position, add initial plies */
7034                 if( count == backwardMostMove )
7035                     count -= initialRulePlies;
7036                 count = forwardMostMove - count;
7037                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7038                         // adjust reversible move counter for checks in Xiangqi
7039                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7040                         if(i < backwardMostMove) i = backwardMostMove;
7041                         while(i <= forwardMostMove) {
7042                                 lastCheck = inCheck; // check evasion does not count
7043                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7044                                 if(inCheck || lastCheck) count--; // check does not count
7045                                 i++;
7046                         }
7047                 }
7048                 if( count >= 100)
7049                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7050                          /* this is used to judge if draw claims are legal */
7051                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7052                          if(engineOpponent) {
7053                            SendToProgram("force\n", engineOpponent); // suppress reply
7054                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7055                          }
7056                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7057                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7058                          return 1;
7059                 }
7060
7061                 /* if draw offer is pending, treat it as a draw claim
7062                  * when draw condition present, to allow engines a way to
7063                  * claim draws before making their move to avoid a race
7064                  * condition occurring after their move
7065                  */
7066                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7067                          char *p = NULL;
7068                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7069                              p = "Draw claim: 50-move rule";
7070                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7071                              p = "Draw claim: 3-fold repetition";
7072                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7073                              p = "Draw claim: insufficient mating material";
7074                          if( p != NULL && canAdjudicate) {
7075                              if(engineOpponent) {
7076                                SendToProgram("force\n", engineOpponent); // suppress reply
7077                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7078                              }
7079                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7080                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7081                              return 1;
7082                          }
7083                 }
7084
7085                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7086                     if(engineOpponent) {
7087                       SendToProgram("force\n", engineOpponent); // suppress reply
7088                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7089                     }
7090                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7091                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7092                     return 1;
7093                 }
7094         return 0;
7095 }
7096
7097 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7098 {   // [HGM] book: this routine intercepts moves to simulate book replies
7099     char *bookHit = NULL;
7100
7101     //first determine if the incoming move brings opponent into his book
7102     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7103         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7104     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7105     if(bookHit != NULL && !cps->bookSuspend) {
7106         // make sure opponent is not going to reply after receiving move to book position
7107         SendToProgram("force\n", cps);
7108         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7109     }
7110     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7111     // now arrange restart after book miss
7112     if(bookHit) {
7113         // after a book hit we never send 'go', and the code after the call to this routine
7114         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7115         char buf[MSG_SIZ];
7116         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7117         SendToProgram(buf, cps);
7118         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7119     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7120         SendToProgram("go\n", cps);
7121         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7122     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7123         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7124             SendToProgram("go\n", cps);
7125         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7126     }
7127     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7128 }
7129
7130 char *savedMessage;
7131 ChessProgramState *savedState;
7132 void DeferredBookMove(void)
7133 {
7134         if(savedState->lastPing != savedState->lastPong)
7135                     ScheduleDelayedEvent(DeferredBookMove, 10);
7136         else
7137         HandleMachineMove(savedMessage, savedState);
7138 }
7139
7140 void
7141 HandleMachineMove(message, cps)
7142      char *message;
7143      ChessProgramState *cps;
7144 {
7145     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7146     char realname[MSG_SIZ];
7147     int fromX, fromY, toX, toY;
7148     ChessMove moveType;
7149     char promoChar;
7150     char *p;
7151     int machineWhite;
7152     char *bookHit;
7153
7154     cps->userError = 0;
7155
7156 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7157     /*
7158      * Kludge to ignore BEL characters
7159      */
7160     while (*message == '\007') message++;
7161
7162     /*
7163      * [HGM] engine debug message: ignore lines starting with '#' character
7164      */
7165     if(cps->debug && *message == '#') return;
7166
7167     /*
7168      * Look for book output
7169      */
7170     if (cps == &first && bookRequested) {
7171         if (message[0] == '\t' || message[0] == ' ') {
7172             /* Part of the book output is here; append it */
7173             strcat(bookOutput, message);
7174             strcat(bookOutput, "  \n");
7175             return;
7176         } else if (bookOutput[0] != NULLCHAR) {
7177             /* All of book output has arrived; display it */
7178             char *p = bookOutput;
7179             while (*p != NULLCHAR) {
7180                 if (*p == '\t') *p = ' ';
7181                 p++;
7182             }
7183             DisplayInformation(bookOutput);
7184             bookRequested = FALSE;
7185             /* Fall through to parse the current output */
7186         }
7187     }
7188
7189     /*
7190      * Look for machine move.
7191      */
7192     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7193         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7194     {
7195         /* This method is only useful on engines that support ping */
7196         if (cps->lastPing != cps->lastPong) {
7197           if (gameMode == BeginningOfGame) {
7198             /* Extra move from before last new; ignore */
7199             if (appData.debugMode) {
7200                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7201             }
7202           } else {
7203             if (appData.debugMode) {
7204                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7205                         cps->which, gameMode);
7206             }
7207
7208             SendToProgram("undo\n", cps);
7209           }
7210           return;
7211         }
7212
7213         switch (gameMode) {
7214           case BeginningOfGame:
7215             /* Extra move from before last reset; ignore */
7216             if (appData.debugMode) {
7217                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7218             }
7219             return;
7220
7221           case EndOfGame:
7222           case IcsIdle:
7223           default:
7224             /* Extra move after we tried to stop.  The mode test is
7225                not a reliable way of detecting this problem, but it's
7226                the best we can do on engines that don't support ping.
7227             */
7228             if (appData.debugMode) {
7229                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7230                         cps->which, gameMode);
7231             }
7232             SendToProgram("undo\n", cps);
7233             return;
7234
7235           case MachinePlaysWhite:
7236           case IcsPlayingWhite:
7237             machineWhite = TRUE;
7238             break;
7239
7240           case MachinePlaysBlack:
7241           case IcsPlayingBlack:
7242             machineWhite = FALSE;
7243             break;
7244
7245           case TwoMachinesPlay:
7246             machineWhite = (cps->twoMachinesColor[0] == 'w');
7247             break;
7248         }
7249         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7250             if (appData.debugMode) {
7251                 fprintf(debugFP,
7252                         "Ignoring move out of turn by %s, gameMode %d"
7253                         ", forwardMost %d\n",
7254                         cps->which, gameMode, forwardMostMove);
7255             }
7256             return;
7257         }
7258
7259     if (appData.debugMode) { int f = forwardMostMove;
7260         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7261                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7262                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7263     }
7264         if(cps->alphaRank) AlphaRank(machineMove, 4);
7265         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7266                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7267             /* Machine move could not be parsed; ignore it. */
7268           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7269                     machineMove, cps->which);
7270             DisplayError(buf1, 0);
7271             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7272                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7273             if (gameMode == TwoMachinesPlay) {
7274               GameEnds(machineWhite ? BlackWins : WhiteWins,
7275                        buf1, GE_XBOARD);
7276             }
7277             return;
7278         }
7279
7280         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7281         /* So we have to redo legality test with true e.p. status here,  */
7282         /* to make sure an illegal e.p. capture does not slip through,   */
7283         /* to cause a forfeit on a justified illegal-move complaint      */
7284         /* of the opponent.                                              */
7285         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7286            ChessMove moveType;
7287            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7288                              fromY, fromX, toY, toX, promoChar);
7289             if (appData.debugMode) {
7290                 int i;
7291                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7292                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7293                 fprintf(debugFP, "castling rights\n");
7294             }
7295             if(moveType == IllegalMove) {
7296               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7297                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7298                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7299                            buf1, GE_XBOARD);
7300                 return;
7301            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7302            /* [HGM] Kludge to handle engines that send FRC-style castling
7303               when they shouldn't (like TSCP-Gothic) */
7304            switch(moveType) {
7305              case WhiteASideCastleFR:
7306              case BlackASideCastleFR:
7307                toX+=2;
7308                currentMoveString[2]++;
7309                break;
7310              case WhiteHSideCastleFR:
7311              case BlackHSideCastleFR:
7312                toX--;
7313                currentMoveString[2]--;
7314                break;
7315              default: ; // nothing to do, but suppresses warning of pedantic compilers
7316            }
7317         }
7318         hintRequested = FALSE;
7319         lastHint[0] = NULLCHAR;
7320         bookRequested = FALSE;
7321         /* Program may be pondering now */
7322         cps->maybeThinking = TRUE;
7323         if (cps->sendTime == 2) cps->sendTime = 1;
7324         if (cps->offeredDraw) cps->offeredDraw--;
7325
7326         /* currentMoveString is set as a side-effect of ParseOneMove */
7327         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7328         strcat(machineMove, "\n");
7329         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7330
7331         /* [AS] Save move info*/
7332         pvInfoList[ forwardMostMove ].score = programStats.score;
7333         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7334         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7335
7336         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7337
7338         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7339         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7340             int count = 0;
7341
7342             while( count < adjudicateLossPlies ) {
7343                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7344
7345                 if( count & 1 ) {
7346                     score = -score; /* Flip score for winning side */
7347                 }
7348
7349                 if( score > adjudicateLossThreshold ) {
7350                     break;
7351                 }
7352
7353                 count++;
7354             }
7355
7356             if( count >= adjudicateLossPlies ) {
7357                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7358
7359                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7360                     "Xboard adjudication",
7361                     GE_XBOARD );
7362
7363                 return;
7364             }
7365         }
7366
7367         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7368
7369 #if ZIPPY
7370         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7371             first.initDone) {
7372           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7373                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7374                 SendToICS("draw ");
7375                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7376           }
7377           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7378           ics_user_moved = 1;
7379           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7380                 char buf[3*MSG_SIZ];
7381
7382                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7383                         programStats.score / 100.,
7384                         programStats.depth,
7385                         programStats.time / 100.,
7386                         (unsigned int)programStats.nodes,
7387                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7388                         programStats.movelist);
7389                 SendToICS(buf);
7390 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7391           }
7392         }
7393 #endif
7394
7395         /* [AS] Clear stats for next move */
7396         ClearProgramStats();
7397         thinkOutput[0] = NULLCHAR;
7398         hiddenThinkOutputState = 0;
7399
7400         bookHit = NULL;
7401         if (gameMode == TwoMachinesPlay) {
7402             /* [HGM] relaying draw offers moved to after reception of move */
7403             /* and interpreting offer as claim if it brings draw condition */
7404             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7405                 SendToProgram("draw\n", cps->other);
7406             }
7407             if (cps->other->sendTime) {
7408                 SendTimeRemaining(cps->other,
7409                                   cps->other->twoMachinesColor[0] == 'w');
7410             }
7411             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7412             if (firstMove && !bookHit) {
7413                 firstMove = FALSE;
7414                 if (cps->other->useColors) {
7415                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7416                 }
7417                 SendToProgram("go\n", cps->other);
7418             }
7419             cps->other->maybeThinking = TRUE;
7420         }
7421
7422         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7423
7424         if (!pausing && appData.ringBellAfterMoves) {
7425             RingBell();
7426         }
7427
7428         /*
7429          * Reenable menu items that were disabled while
7430          * machine was thinking
7431          */
7432         if (gameMode != TwoMachinesPlay)
7433             SetUserThinkingEnables();
7434
7435         // [HGM] book: after book hit opponent has received move and is now in force mode
7436         // force the book reply into it, and then fake that it outputted this move by jumping
7437         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7438         if(bookHit) {
7439                 static char bookMove[MSG_SIZ]; // a bit generous?
7440
7441                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7442                 strcat(bookMove, bookHit);
7443                 message = bookMove;
7444                 cps = cps->other;
7445                 programStats.nodes = programStats.depth = programStats.time =
7446                 programStats.score = programStats.got_only_move = 0;
7447                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7448
7449                 if(cps->lastPing != cps->lastPong) {
7450                     savedMessage = message; // args for deferred call
7451                     savedState = cps;
7452                     ScheduleDelayedEvent(DeferredBookMove, 10);
7453                     return;
7454                 }
7455                 goto FakeBookMove;
7456         }
7457
7458         return;
7459     }
7460
7461     /* Set special modes for chess engines.  Later something general
7462      *  could be added here; for now there is just one kludge feature,
7463      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7464      *  when "xboard" is given as an interactive command.
7465      */
7466     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7467         cps->useSigint = FALSE;
7468         cps->useSigterm = FALSE;
7469     }
7470     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7471       ParseFeatures(message+8, cps);
7472       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7473     }
7474
7475     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7476       int dummy, s=6; char buf[MSG_SIZ];
7477       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7478       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7479       ParseFEN(boards[0], &dummy, message+s);
7480       DrawPosition(TRUE, boards[0]);
7481       startedFromSetupPosition = TRUE;
7482       return;
7483     }
7484     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7485      * want this, I was asked to put it in, and obliged.
7486      */
7487     if (!strncmp(message, "setboard ", 9)) {
7488         Board initial_position;
7489
7490         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7491
7492         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7493             DisplayError(_("Bad FEN received from engine"), 0);
7494             return ;
7495         } else {
7496            Reset(TRUE, FALSE);
7497            CopyBoard(boards[0], initial_position);
7498            initialRulePlies = FENrulePlies;
7499            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7500            else gameMode = MachinePlaysBlack;
7501            DrawPosition(FALSE, boards[currentMove]);
7502         }
7503         return;
7504     }
7505
7506     /*
7507      * Look for communication commands
7508      */
7509     if (!strncmp(message, "telluser ", 9)) {
7510         if(message[9] == '\\' && message[10] == '\\')
7511             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7512         DisplayNote(message + 9);
7513         return;
7514     }
7515     if (!strncmp(message, "tellusererror ", 14)) {
7516         cps->userError = 1;
7517         if(message[14] == '\\' && message[15] == '\\')
7518             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7519         DisplayError(message + 14, 0);
7520         return;
7521     }
7522     if (!strncmp(message, "tellopponent ", 13)) {
7523       if (appData.icsActive) {
7524         if (loggedOn) {
7525           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7526           SendToICS(buf1);
7527         }
7528       } else {
7529         DisplayNote(message + 13);
7530       }
7531       return;
7532     }
7533     if (!strncmp(message, "tellothers ", 11)) {
7534       if (appData.icsActive) {
7535         if (loggedOn) {
7536           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7537           SendToICS(buf1);
7538         }
7539       }
7540       return;
7541     }
7542     if (!strncmp(message, "tellall ", 8)) {
7543       if (appData.icsActive) {
7544         if (loggedOn) {
7545           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7546           SendToICS(buf1);
7547         }
7548       } else {
7549         DisplayNote(message + 8);
7550       }
7551       return;
7552     }
7553     if (strncmp(message, "warning", 7) == 0) {
7554         /* Undocumented feature, use tellusererror in new code */
7555         DisplayError(message, 0);
7556         return;
7557     }
7558     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7559         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7560         strcat(realname, " query");
7561         AskQuestion(realname, buf2, buf1, cps->pr);
7562         return;
7563     }
7564     /* Commands from the engine directly to ICS.  We don't allow these to be
7565      *  sent until we are logged on. Crafty kibitzes have been known to
7566      *  interfere with the login process.
7567      */
7568     if (loggedOn) {
7569         if (!strncmp(message, "tellics ", 8)) {
7570             SendToICS(message + 8);
7571             SendToICS("\n");
7572             return;
7573         }
7574         if (!strncmp(message, "tellicsnoalias ", 15)) {
7575             SendToICS(ics_prefix);
7576             SendToICS(message + 15);
7577             SendToICS("\n");
7578             return;
7579         }
7580         /* The following are for backward compatibility only */
7581         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7582             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7583             SendToICS(ics_prefix);
7584             SendToICS(message);
7585             SendToICS("\n");
7586             return;
7587         }
7588     }
7589     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7590         return;
7591     }
7592     /*
7593      * If the move is illegal, cancel it and redraw the board.
7594      * Also deal with other error cases.  Matching is rather loose
7595      * here to accommodate engines written before the spec.
7596      */
7597     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7598         strncmp(message, "Error", 5) == 0) {
7599         if (StrStr(message, "name") ||
7600             StrStr(message, "rating") || StrStr(message, "?") ||
7601             StrStr(message, "result") || StrStr(message, "board") ||
7602             StrStr(message, "bk") || StrStr(message, "computer") ||
7603             StrStr(message, "variant") || StrStr(message, "hint") ||
7604             StrStr(message, "random") || StrStr(message, "depth") ||
7605             StrStr(message, "accepted")) {
7606             return;
7607         }
7608         if (StrStr(message, "protover")) {
7609           /* Program is responding to input, so it's apparently done
7610              initializing, and this error message indicates it is
7611              protocol version 1.  So we don't need to wait any longer
7612              for it to initialize and send feature commands. */
7613           FeatureDone(cps, 1);
7614           cps->protocolVersion = 1;
7615           return;
7616         }
7617         cps->maybeThinking = FALSE;
7618
7619         if (StrStr(message, "draw")) {
7620             /* Program doesn't have "draw" command */
7621             cps->sendDrawOffers = 0;
7622             return;
7623         }
7624         if (cps->sendTime != 1 &&
7625             (StrStr(message, "time") || StrStr(message, "otim"))) {
7626           /* Program apparently doesn't have "time" or "otim" command */
7627           cps->sendTime = 0;
7628           return;
7629         }
7630         if (StrStr(message, "analyze")) {
7631             cps->analysisSupport = FALSE;
7632             cps->analyzing = FALSE;
7633             Reset(FALSE, TRUE);
7634             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7635             DisplayError(buf2, 0);
7636             return;
7637         }
7638         if (StrStr(message, "(no matching move)st")) {
7639           /* Special kludge for GNU Chess 4 only */
7640           cps->stKludge = TRUE;
7641           SendTimeControl(cps, movesPerSession, timeControl,
7642                           timeIncrement, appData.searchDepth,
7643                           searchTime);
7644           return;
7645         }
7646         if (StrStr(message, "(no matching move)sd")) {
7647           /* Special kludge for GNU Chess 4 only */
7648           cps->sdKludge = TRUE;
7649           SendTimeControl(cps, movesPerSession, timeControl,
7650                           timeIncrement, appData.searchDepth,
7651                           searchTime);
7652           return;
7653         }
7654         if (!StrStr(message, "llegal")) {
7655             return;
7656         }
7657         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7658             gameMode == IcsIdle) return;
7659         if (forwardMostMove <= backwardMostMove) return;
7660         if (pausing) PauseEvent();
7661       if(appData.forceIllegal) {
7662             // [HGM] illegal: machine refused move; force position after move into it
7663           SendToProgram("force\n", cps);
7664           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7665                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7666                 // when black is to move, while there might be nothing on a2 or black
7667                 // might already have the move. So send the board as if white has the move.
7668                 // But first we must change the stm of the engine, as it refused the last move
7669                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7670                 if(WhiteOnMove(forwardMostMove)) {
7671                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7672                     SendBoard(cps, forwardMostMove); // kludgeless board
7673                 } else {
7674                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7675                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7676                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7677                 }
7678           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7679             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7680                  gameMode == TwoMachinesPlay)
7681               SendToProgram("go\n", cps);
7682             return;
7683       } else
7684         if (gameMode == PlayFromGameFile) {
7685             /* Stop reading this game file */
7686             gameMode = EditGame;
7687             ModeHighlight();
7688         }
7689         currentMove = forwardMostMove-1;
7690         DisplayMove(currentMove-1); /* before DisplayMoveError */
7691         SwitchClocks(forwardMostMove-1); // [HGM] race
7692         DisplayBothClocks();
7693         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7694                 parseList[currentMove], cps->which);
7695         DisplayMoveError(buf1);
7696         DrawPosition(FALSE, boards[currentMove]);
7697
7698         /* [HGM] illegal-move claim should forfeit game when Xboard */
7699         /* only passes fully legal moves                            */
7700         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7701             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7702                                 "False illegal-move claim", GE_XBOARD );
7703         }
7704         return;
7705     }
7706     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7707         /* Program has a broken "time" command that
7708            outputs a string not ending in newline.
7709            Don't use it. */
7710         cps->sendTime = 0;
7711     }
7712
7713     /*
7714      * If chess program startup fails, exit with an error message.
7715      * Attempts to recover here are futile.
7716      */
7717     if ((StrStr(message, "unknown host") != NULL)
7718         || (StrStr(message, "No remote directory") != NULL)
7719         || (StrStr(message, "not found") != NULL)
7720         || (StrStr(message, "No such file") != NULL)
7721         || (StrStr(message, "can't alloc") != NULL)
7722         || (StrStr(message, "Permission denied") != NULL)) {
7723
7724         cps->maybeThinking = FALSE;
7725         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7726                 cps->which, cps->program, cps->host, message);
7727         RemoveInputSource(cps->isr);
7728         DisplayFatalError(buf1, 0, 1);
7729         return;
7730     }
7731
7732     /*
7733      * Look for hint output
7734      */
7735     if (sscanf(message, "Hint: %s", buf1) == 1) {
7736         if (cps == &first && hintRequested) {
7737             hintRequested = FALSE;
7738             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7739                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7740                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7741                                     PosFlags(forwardMostMove),
7742                                     fromY, fromX, toY, toX, promoChar, buf1);
7743                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7744                 DisplayInformation(buf2);
7745             } else {
7746                 /* Hint move could not be parsed!? */
7747               snprintf(buf2, sizeof(buf2),
7748                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7749                         buf1, cps->which);
7750                 DisplayError(buf2, 0);
7751             }
7752         } else {
7753           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7754         }
7755         return;
7756     }
7757
7758     /*
7759      * Ignore other messages if game is not in progress
7760      */
7761     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7762         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7763
7764     /*
7765      * look for win, lose, draw, or draw offer
7766      */
7767     if (strncmp(message, "1-0", 3) == 0) {
7768         char *p, *q, *r = "";
7769         p = strchr(message, '{');
7770         if (p) {
7771             q = strchr(p, '}');
7772             if (q) {
7773                 *q = NULLCHAR;
7774                 r = p + 1;
7775             }
7776         }
7777         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7778         return;
7779     } else if (strncmp(message, "0-1", 3) == 0) {
7780         char *p, *q, *r = "";
7781         p = strchr(message, '{');
7782         if (p) {
7783             q = strchr(p, '}');
7784             if (q) {
7785                 *q = NULLCHAR;
7786                 r = p + 1;
7787             }
7788         }
7789         /* Kludge for Arasan 4.1 bug */
7790         if (strcmp(r, "Black resigns") == 0) {
7791             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7792             return;
7793         }
7794         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7795         return;
7796     } else if (strncmp(message, "1/2", 3) == 0) {
7797         char *p, *q, *r = "";
7798         p = strchr(message, '{');
7799         if (p) {
7800             q = strchr(p, '}');
7801             if (q) {
7802                 *q = NULLCHAR;
7803                 r = p + 1;
7804             }
7805         }
7806
7807         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7808         return;
7809
7810     } else if (strncmp(message, "White resign", 12) == 0) {
7811         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7812         return;
7813     } else if (strncmp(message, "Black resign", 12) == 0) {
7814         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7815         return;
7816     } else if (strncmp(message, "White matches", 13) == 0 ||
7817                strncmp(message, "Black matches", 13) == 0   ) {
7818         /* [HGM] ignore GNUShogi noises */
7819         return;
7820     } else if (strncmp(message, "White", 5) == 0 &&
7821                message[5] != '(' &&
7822                StrStr(message, "Black") == NULL) {
7823         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7824         return;
7825     } else if (strncmp(message, "Black", 5) == 0 &&
7826                message[5] != '(') {
7827         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7828         return;
7829     } else if (strcmp(message, "resign") == 0 ||
7830                strcmp(message, "computer resigns") == 0) {
7831         switch (gameMode) {
7832           case MachinePlaysBlack:
7833           case IcsPlayingBlack:
7834             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7835             break;
7836           case MachinePlaysWhite:
7837           case IcsPlayingWhite:
7838             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7839             break;
7840           case TwoMachinesPlay:
7841             if (cps->twoMachinesColor[0] == 'w')
7842               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7843             else
7844               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7845             break;
7846           default:
7847             /* can't happen */
7848             break;
7849         }
7850         return;
7851     } else if (strncmp(message, "opponent mates", 14) == 0) {
7852         switch (gameMode) {
7853           case MachinePlaysBlack:
7854           case IcsPlayingBlack:
7855             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7856             break;
7857           case MachinePlaysWhite:
7858           case IcsPlayingWhite:
7859             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7860             break;
7861           case TwoMachinesPlay:
7862             if (cps->twoMachinesColor[0] == 'w')
7863               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7864             else
7865               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7866             break;
7867           default:
7868             /* can't happen */
7869             break;
7870         }
7871         return;
7872     } else if (strncmp(message, "computer mates", 14) == 0) {
7873         switch (gameMode) {
7874           case MachinePlaysBlack:
7875           case IcsPlayingBlack:
7876             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7877             break;
7878           case MachinePlaysWhite:
7879           case IcsPlayingWhite:
7880             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7881             break;
7882           case TwoMachinesPlay:
7883             if (cps->twoMachinesColor[0] == 'w')
7884               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7885             else
7886               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7887             break;
7888           default:
7889             /* can't happen */
7890             break;
7891         }
7892         return;
7893     } else if (strncmp(message, "checkmate", 9) == 0) {
7894         if (WhiteOnMove(forwardMostMove)) {
7895             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7896         } else {
7897             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7898         }
7899         return;
7900     } else if (strstr(message, "Draw") != NULL ||
7901                strstr(message, "game is a draw") != NULL) {
7902         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7903         return;
7904     } else if (strstr(message, "offer") != NULL &&
7905                strstr(message, "draw") != NULL) {
7906 #if ZIPPY
7907         if (appData.zippyPlay && first.initDone) {
7908             /* Relay offer to ICS */
7909             SendToICS(ics_prefix);
7910             SendToICS("draw\n");
7911         }
7912 #endif
7913         cps->offeredDraw = 2; /* valid until this engine moves twice */
7914         if (gameMode == TwoMachinesPlay) {
7915             if (cps->other->offeredDraw) {
7916                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7917             /* [HGM] in two-machine mode we delay relaying draw offer      */
7918             /* until after we also have move, to see if it is really claim */
7919             }
7920         } else if (gameMode == MachinePlaysWhite ||
7921                    gameMode == MachinePlaysBlack) {
7922           if (userOfferedDraw) {
7923             DisplayInformation(_("Machine accepts your draw offer"));
7924             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7925           } else {
7926             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7927           }
7928         }
7929     }
7930
7931
7932     /*
7933      * Look for thinking output
7934      */
7935     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7936           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7937                                 ) {
7938         int plylev, mvleft, mvtot, curscore, time;
7939         char mvname[MOVE_LEN];
7940         u64 nodes; // [DM]
7941         char plyext;
7942         int ignore = FALSE;
7943         int prefixHint = FALSE;
7944         mvname[0] = NULLCHAR;
7945
7946         switch (gameMode) {
7947           case MachinePlaysBlack:
7948           case IcsPlayingBlack:
7949             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7950             break;
7951           case MachinePlaysWhite:
7952           case IcsPlayingWhite:
7953             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7954             break;
7955           case AnalyzeMode:
7956           case AnalyzeFile:
7957             break;
7958           case IcsObserving: /* [DM] icsEngineAnalyze */
7959             if (!appData.icsEngineAnalyze) ignore = TRUE;
7960             break;
7961           case TwoMachinesPlay:
7962             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7963                 ignore = TRUE;
7964             }
7965             break;
7966           default:
7967             ignore = TRUE;
7968             break;
7969         }
7970
7971         if (!ignore) {
7972             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7973             buf1[0] = NULLCHAR;
7974             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7975                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7976
7977                 if (plyext != ' ' && plyext != '\t') {
7978                     time *= 100;
7979                 }
7980
7981                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7982                 if( cps->scoreIsAbsolute &&
7983                     ( gameMode == MachinePlaysBlack ||
7984                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7985                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7986                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7987                      !WhiteOnMove(currentMove)
7988                     ) )
7989                 {
7990                     curscore = -curscore;
7991                 }
7992
7993
7994                 tempStats.depth = plylev;
7995                 tempStats.nodes = nodes;
7996                 tempStats.time = time;
7997                 tempStats.score = curscore;
7998                 tempStats.got_only_move = 0;
7999
8000                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8001                         int ticklen;
8002
8003                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8004                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8005                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8006                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8007                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8008                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8009                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8010                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8011                 }
8012
8013                 /* Buffer overflow protection */
8014                 if (buf1[0] != NULLCHAR) {
8015                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8016                         && appData.debugMode) {
8017                         fprintf(debugFP,
8018                                 "PV is too long; using the first %u bytes.\n",
8019                                 (unsigned) sizeof(tempStats.movelist) - 1);
8020                     }
8021
8022                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8023                 } else {
8024                     sprintf(tempStats.movelist, " no PV\n");
8025                 }
8026
8027                 if (tempStats.seen_stat) {
8028                     tempStats.ok_to_send = 1;
8029                 }
8030
8031                 if (strchr(tempStats.movelist, '(') != NULL) {
8032                     tempStats.line_is_book = 1;
8033                     tempStats.nr_moves = 0;
8034                     tempStats.moves_left = 0;
8035                 } else {
8036                     tempStats.line_is_book = 0;
8037                 }
8038
8039                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8040                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8041
8042                 SendProgramStatsToFrontend( cps, &tempStats );
8043
8044                 /*
8045                     [AS] Protect the thinkOutput buffer from overflow... this
8046                     is only useful if buf1 hasn't overflowed first!
8047                 */
8048                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8049                          plylev,
8050                          (gameMode == TwoMachinesPlay ?
8051                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8052                          ((double) curscore) / 100.0,
8053                          prefixHint ? lastHint : "",
8054                          prefixHint ? " " : "" );
8055
8056                 if( buf1[0] != NULLCHAR ) {
8057                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8058
8059                     if( strlen(buf1) > max_len ) {
8060                         if( appData.debugMode) {
8061                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8062                         }
8063                         buf1[max_len+1] = '\0';
8064                     }
8065
8066                     strcat( thinkOutput, buf1 );
8067                 }
8068
8069                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8070                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8071                     DisplayMove(currentMove - 1);
8072                 }
8073                 return;
8074
8075             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8076                 /* crafty (9.25+) says "(only move) <move>"
8077                  * if there is only 1 legal move
8078                  */
8079                 sscanf(p, "(only move) %s", buf1);
8080                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8081                 sprintf(programStats.movelist, "%s (only move)", buf1);
8082                 programStats.depth = 1;
8083                 programStats.nr_moves = 1;
8084                 programStats.moves_left = 1;
8085                 programStats.nodes = 1;
8086                 programStats.time = 1;
8087                 programStats.got_only_move = 1;
8088
8089                 /* Not really, but we also use this member to
8090                    mean "line isn't going to change" (Crafty
8091                    isn't searching, so stats won't change) */
8092                 programStats.line_is_book = 1;
8093
8094                 SendProgramStatsToFrontend( cps, &programStats );
8095
8096                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8097                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8098                     DisplayMove(currentMove - 1);
8099                 }
8100                 return;
8101             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8102                               &time, &nodes, &plylev, &mvleft,
8103                               &mvtot, mvname) >= 5) {
8104                 /* The stat01: line is from Crafty (9.29+) in response
8105                    to the "." command */
8106                 programStats.seen_stat = 1;
8107                 cps->maybeThinking = TRUE;
8108
8109                 if (programStats.got_only_move || !appData.periodicUpdates)
8110                   return;
8111
8112                 programStats.depth = plylev;
8113                 programStats.time = time;
8114                 programStats.nodes = nodes;
8115                 programStats.moves_left = mvleft;
8116                 programStats.nr_moves = mvtot;
8117                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8118                 programStats.ok_to_send = 1;
8119                 programStats.movelist[0] = '\0';
8120
8121                 SendProgramStatsToFrontend( cps, &programStats );
8122
8123                 return;
8124
8125             } else if (strncmp(message,"++",2) == 0) {
8126                 /* Crafty 9.29+ outputs this */
8127                 programStats.got_fail = 2;
8128                 return;
8129
8130             } else if (strncmp(message,"--",2) == 0) {
8131                 /* Crafty 9.29+ outputs this */
8132                 programStats.got_fail = 1;
8133                 return;
8134
8135             } else if (thinkOutput[0] != NULLCHAR &&
8136                        strncmp(message, "    ", 4) == 0) {
8137                 unsigned message_len;
8138
8139                 p = message;
8140                 while (*p && *p == ' ') p++;
8141
8142                 message_len = strlen( p );
8143
8144                 /* [AS] Avoid buffer overflow */
8145                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8146                     strcat(thinkOutput, " ");
8147                     strcat(thinkOutput, p);
8148                 }
8149
8150                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8151                     strcat(programStats.movelist, " ");
8152                     strcat(programStats.movelist, p);
8153                 }
8154
8155                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8156                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8157                     DisplayMove(currentMove - 1);
8158                 }
8159                 return;
8160             }
8161         }
8162         else {
8163             buf1[0] = NULLCHAR;
8164
8165             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8166                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8167             {
8168                 ChessProgramStats cpstats;
8169
8170                 if (plyext != ' ' && plyext != '\t') {
8171                     time *= 100;
8172                 }
8173
8174                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8175                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8176                     curscore = -curscore;
8177                 }
8178
8179                 cpstats.depth = plylev;
8180                 cpstats.nodes = nodes;
8181                 cpstats.time = time;
8182                 cpstats.score = curscore;
8183                 cpstats.got_only_move = 0;
8184                 cpstats.movelist[0] = '\0';
8185
8186                 if (buf1[0] != NULLCHAR) {
8187                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8188                 }
8189
8190                 cpstats.ok_to_send = 0;
8191                 cpstats.line_is_book = 0;
8192                 cpstats.nr_moves = 0;
8193                 cpstats.moves_left = 0;
8194
8195                 SendProgramStatsToFrontend( cps, &cpstats );
8196             }
8197         }
8198     }
8199 }
8200
8201
8202 /* Parse a game score from the character string "game", and
8203    record it as the history of the current game.  The game
8204    score is NOT assumed to start from the standard position.
8205    The display is not updated in any way.
8206    */
8207 void
8208 ParseGameHistory(game)
8209      char *game;
8210 {
8211     ChessMove moveType;
8212     int fromX, fromY, toX, toY, boardIndex;
8213     char promoChar;
8214     char *p, *q;
8215     char buf[MSG_SIZ];
8216
8217     if (appData.debugMode)
8218       fprintf(debugFP, "Parsing game history: %s\n", game);
8219
8220     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8221     gameInfo.site = StrSave(appData.icsHost);
8222     gameInfo.date = PGNDate();
8223     gameInfo.round = StrSave("-");
8224
8225     /* Parse out names of players */
8226     while (*game == ' ') game++;
8227     p = buf;
8228     while (*game != ' ') *p++ = *game++;
8229     *p = NULLCHAR;
8230     gameInfo.white = StrSave(buf);
8231     while (*game == ' ') game++;
8232     p = buf;
8233     while (*game != ' ' && *game != '\n') *p++ = *game++;
8234     *p = NULLCHAR;
8235     gameInfo.black = StrSave(buf);
8236
8237     /* Parse moves */
8238     boardIndex = blackPlaysFirst ? 1 : 0;
8239     yynewstr(game);
8240     for (;;) {
8241         yyboardindex = boardIndex;
8242         moveType = (ChessMove) Myylex();
8243         switch (moveType) {
8244           case IllegalMove:             /* maybe suicide chess, etc. */
8245   if (appData.debugMode) {
8246     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8247     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8248     setbuf(debugFP, NULL);
8249   }
8250           case WhitePromotion:
8251           case BlackPromotion:
8252           case WhiteNonPromotion:
8253           case BlackNonPromotion:
8254           case NormalMove:
8255           case WhiteCapturesEnPassant:
8256           case BlackCapturesEnPassant:
8257           case WhiteKingSideCastle:
8258           case WhiteQueenSideCastle:
8259           case BlackKingSideCastle:
8260           case BlackQueenSideCastle:
8261           case WhiteKingSideCastleWild:
8262           case WhiteQueenSideCastleWild:
8263           case BlackKingSideCastleWild:
8264           case BlackQueenSideCastleWild:
8265           /* PUSH Fabien */
8266           case WhiteHSideCastleFR:
8267           case WhiteASideCastleFR:
8268           case BlackHSideCastleFR:
8269           case BlackASideCastleFR:
8270           /* POP Fabien */
8271             fromX = currentMoveString[0] - AAA;
8272             fromY = currentMoveString[1] - ONE;
8273             toX = currentMoveString[2] - AAA;
8274             toY = currentMoveString[3] - ONE;
8275             promoChar = currentMoveString[4];
8276             break;
8277           case WhiteDrop:
8278           case BlackDrop:
8279             fromX = moveType == WhiteDrop ?
8280               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8281             (int) CharToPiece(ToLower(currentMoveString[0]));
8282             fromY = DROP_RANK;
8283             toX = currentMoveString[2] - AAA;
8284             toY = currentMoveString[3] - ONE;
8285             promoChar = NULLCHAR;
8286             break;
8287           case AmbiguousMove:
8288             /* bug? */
8289             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8290   if (appData.debugMode) {
8291     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8292     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8293     setbuf(debugFP, NULL);
8294   }
8295             DisplayError(buf, 0);
8296             return;
8297           case ImpossibleMove:
8298             /* bug? */
8299             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8300   if (appData.debugMode) {
8301     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8302     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8303     setbuf(debugFP, NULL);
8304   }
8305             DisplayError(buf, 0);
8306             return;
8307           case EndOfFile:
8308             if (boardIndex < backwardMostMove) {
8309                 /* Oops, gap.  How did that happen? */
8310                 DisplayError(_("Gap in move list"), 0);
8311                 return;
8312             }
8313             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8314             if (boardIndex > forwardMostMove) {
8315                 forwardMostMove = boardIndex;
8316             }
8317             return;
8318           case ElapsedTime:
8319             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8320                 strcat(parseList[boardIndex-1], " ");
8321                 strcat(parseList[boardIndex-1], yy_text);
8322             }
8323             continue;
8324           case Comment:
8325           case PGNTag:
8326           case NAG:
8327           default:
8328             /* ignore */
8329             continue;
8330           case WhiteWins:
8331           case BlackWins:
8332           case GameIsDrawn:
8333           case GameUnfinished:
8334             if (gameMode == IcsExamining) {
8335                 if (boardIndex < backwardMostMove) {
8336                     /* Oops, gap.  How did that happen? */
8337                     return;
8338                 }
8339                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8340                 return;
8341             }
8342             gameInfo.result = moveType;
8343             p = strchr(yy_text, '{');
8344             if (p == NULL) p = strchr(yy_text, '(');
8345             if (p == NULL) {
8346                 p = yy_text;
8347                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8348             } else {
8349                 q = strchr(p, *p == '{' ? '}' : ')');
8350                 if (q != NULL) *q = NULLCHAR;
8351                 p++;
8352             }
8353             gameInfo.resultDetails = StrSave(p);
8354             continue;
8355         }
8356         if (boardIndex >= forwardMostMove &&
8357             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8358             backwardMostMove = blackPlaysFirst ? 1 : 0;
8359             return;
8360         }
8361         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8362                                  fromY, fromX, toY, toX, promoChar,
8363                                  parseList[boardIndex]);
8364         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8365         /* currentMoveString is set as a side-effect of yylex */
8366         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8367         strcat(moveList[boardIndex], "\n");
8368         boardIndex++;
8369         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8370         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8371           case MT_NONE:
8372           case MT_STALEMATE:
8373           default:
8374             break;
8375           case MT_CHECK:
8376             if(gameInfo.variant != VariantShogi)
8377                 strcat(parseList[boardIndex - 1], "+");
8378             break;
8379           case MT_CHECKMATE:
8380           case MT_STAINMATE:
8381             strcat(parseList[boardIndex - 1], "#");
8382             break;
8383         }
8384     }
8385 }
8386
8387
8388 /* Apply a move to the given board  */
8389 void
8390 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8391      int fromX, fromY, toX, toY;
8392      int promoChar;
8393      Board board;
8394 {
8395   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8396   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8397
8398     /* [HGM] compute & store e.p. status and castling rights for new position */
8399     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8400
8401       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8402       oldEP = (signed char)board[EP_STATUS];
8403       board[EP_STATUS] = EP_NONE;
8404
8405       if( board[toY][toX] != EmptySquare )
8406            board[EP_STATUS] = EP_CAPTURE;
8407
8408   if (fromY == DROP_RANK) {
8409         /* must be first */
8410         piece = board[toY][toX] = (ChessSquare) fromX;
8411   } else {
8412       int i;
8413
8414       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8415            if( gameInfo.variant == VariantFairy ) board[EP_STATUS] = EP_PAWN_MOVE; // Lance in fairy is Pawn-like
8416       } else
8417       if( board[fromY][fromX] == WhitePawn ) {
8418            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8419                board[EP_STATUS] = EP_PAWN_MOVE;
8420            if( toY-fromY==2) {
8421                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8422                         gameInfo.variant != VariantBerolina || toX < fromX)
8423                       board[EP_STATUS] = toX | berolina;
8424                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8425                         gameInfo.variant != VariantBerolina || toX > fromX)
8426                       board[EP_STATUS] = toX;
8427            }
8428       } else
8429       if( board[fromY][fromX] == BlackPawn ) {
8430            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8431                board[EP_STATUS] = EP_PAWN_MOVE;
8432            if( toY-fromY== -2) {
8433                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8434                         gameInfo.variant != VariantBerolina || toX < fromX)
8435                       board[EP_STATUS] = toX | berolina;
8436                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8437                         gameInfo.variant != VariantBerolina || toX > fromX)
8438                       board[EP_STATUS] = toX;
8439            }
8440        }
8441
8442        for(i=0; i<nrCastlingRights; i++) {
8443            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8444               board[CASTLING][i] == toX   && castlingRank[i] == toY
8445              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8446        }
8447
8448      if (fromX == toX && fromY == toY) return;
8449
8450      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8451      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8452      if(gameInfo.variant == VariantKnightmate)
8453          king += (int) WhiteUnicorn - (int) WhiteKing;
8454
8455     /* Code added by Tord: */
8456     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8457     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8458         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8459       board[fromY][fromX] = EmptySquare;
8460       board[toY][toX] = EmptySquare;
8461       if((toX > fromX) != (piece == WhiteRook)) {
8462         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8463       } else {
8464         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8465       }
8466     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8467                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8468       board[fromY][fromX] = EmptySquare;
8469       board[toY][toX] = EmptySquare;
8470       if((toX > fromX) != (piece == BlackRook)) {
8471         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8472       } else {
8473         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8474       }
8475     /* End of code added by Tord */
8476
8477     } else if (board[fromY][fromX] == king
8478         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8479         && toY == fromY && toX > fromX+1) {
8480         board[fromY][fromX] = EmptySquare;
8481         board[toY][toX] = king;
8482         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8483         board[fromY][BOARD_RGHT-1] = EmptySquare;
8484     } else if (board[fromY][fromX] == king
8485         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8486                && toY == fromY && toX < fromX-1) {
8487         board[fromY][fromX] = EmptySquare;
8488         board[toY][toX] = king;
8489         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8490         board[fromY][BOARD_LEFT] = EmptySquare;
8491     } else if (board[fromY][fromX] == WhitePawn
8492                && toY >= BOARD_HEIGHT-promoRank
8493                && gameInfo.variant != VariantXiangqi
8494                ) {
8495         /* white pawn promotion */
8496         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8497         if (board[toY][toX] == EmptySquare) {
8498             board[toY][toX] = WhiteQueen;
8499         }
8500         if(gameInfo.variant==VariantBughouse ||
8501            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8502             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8503         board[fromY][fromX] = EmptySquare;
8504     } else if ((fromY == BOARD_HEIGHT-4)
8505                && (toX != fromX)
8506                && gameInfo.variant != VariantXiangqi
8507                && gameInfo.variant != VariantBerolina
8508                && (board[fromY][fromX] == WhitePawn)
8509                && (board[toY][toX] == EmptySquare)) {
8510         board[fromY][fromX] = EmptySquare;
8511         board[toY][toX] = WhitePawn;
8512         captured = board[toY - 1][toX];
8513         board[toY - 1][toX] = EmptySquare;
8514     } else if ((fromY == BOARD_HEIGHT-4)
8515                && (toX == fromX)
8516                && gameInfo.variant == VariantBerolina
8517                && (board[fromY][fromX] == WhitePawn)
8518                && (board[toY][toX] == EmptySquare)) {
8519         board[fromY][fromX] = EmptySquare;
8520         board[toY][toX] = WhitePawn;
8521         if(oldEP & EP_BEROLIN_A) {
8522                 captured = board[fromY][fromX-1];
8523                 board[fromY][fromX-1] = EmptySquare;
8524         }else{  captured = board[fromY][fromX+1];
8525                 board[fromY][fromX+1] = EmptySquare;
8526         }
8527     } else if (board[fromY][fromX] == king
8528         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8529                && toY == fromY && toX > fromX+1) {
8530         board[fromY][fromX] = EmptySquare;
8531         board[toY][toX] = king;
8532         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8533         board[fromY][BOARD_RGHT-1] = EmptySquare;
8534     } else if (board[fromY][fromX] == king
8535         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8536                && toY == fromY && toX < fromX-1) {
8537         board[fromY][fromX] = EmptySquare;
8538         board[toY][toX] = king;
8539         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8540         board[fromY][BOARD_LEFT] = EmptySquare;
8541     } else if (fromY == 7 && fromX == 3
8542                && board[fromY][fromX] == BlackKing
8543                && toY == 7 && toX == 5) {
8544         board[fromY][fromX] = EmptySquare;
8545         board[toY][toX] = BlackKing;
8546         board[fromY][7] = EmptySquare;
8547         board[toY][4] = BlackRook;
8548     } else if (fromY == 7 && fromX == 3
8549                && board[fromY][fromX] == BlackKing
8550                && toY == 7 && toX == 1) {
8551         board[fromY][fromX] = EmptySquare;
8552         board[toY][toX] = BlackKing;
8553         board[fromY][0] = EmptySquare;
8554         board[toY][2] = BlackRook;
8555     } else if (board[fromY][fromX] == BlackPawn
8556                && toY < promoRank
8557                && gameInfo.variant != VariantXiangqi
8558                ) {
8559         /* black pawn promotion */
8560         board[toY][toX] = CharToPiece(ToLower(promoChar));
8561         if (board[toY][toX] == EmptySquare) {
8562             board[toY][toX] = BlackQueen;
8563         }
8564         if(gameInfo.variant==VariantBughouse ||
8565            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8566             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8567         board[fromY][fromX] = EmptySquare;
8568     } else if ((fromY == 3)
8569                && (toX != fromX)
8570                && gameInfo.variant != VariantXiangqi
8571                && gameInfo.variant != VariantBerolina
8572                && (board[fromY][fromX] == BlackPawn)
8573                && (board[toY][toX] == EmptySquare)) {
8574         board[fromY][fromX] = EmptySquare;
8575         board[toY][toX] = BlackPawn;
8576         captured = board[toY + 1][toX];
8577         board[toY + 1][toX] = EmptySquare;
8578     } else if ((fromY == 3)
8579                && (toX == fromX)
8580                && gameInfo.variant == VariantBerolina
8581                && (board[fromY][fromX] == BlackPawn)
8582                && (board[toY][toX] == EmptySquare)) {
8583         board[fromY][fromX] = EmptySquare;
8584         board[toY][toX] = BlackPawn;
8585         if(oldEP & EP_BEROLIN_A) {
8586                 captured = board[fromY][fromX-1];
8587                 board[fromY][fromX-1] = EmptySquare;
8588         }else{  captured = board[fromY][fromX+1];
8589                 board[fromY][fromX+1] = EmptySquare;
8590         }
8591     } else {
8592         board[toY][toX] = board[fromY][fromX];
8593         board[fromY][fromX] = EmptySquare;
8594     }
8595   }
8596
8597     if (gameInfo.holdingsWidth != 0) {
8598
8599       /* !!A lot more code needs to be written to support holdings  */
8600       /* [HGM] OK, so I have written it. Holdings are stored in the */
8601       /* penultimate board files, so they are automaticlly stored   */
8602       /* in the game history.                                       */
8603       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8604                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8605         /* Delete from holdings, by decreasing count */
8606         /* and erasing image if necessary            */
8607         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8608         if(p < (int) BlackPawn) { /* white drop */
8609              p -= (int)WhitePawn;
8610                  p = PieceToNumber((ChessSquare)p);
8611              if(p >= gameInfo.holdingsSize) p = 0;
8612              if(--board[p][BOARD_WIDTH-2] <= 0)
8613                   board[p][BOARD_WIDTH-1] = EmptySquare;
8614              if((int)board[p][BOARD_WIDTH-2] < 0)
8615                         board[p][BOARD_WIDTH-2] = 0;
8616         } else {                  /* black drop */
8617              p -= (int)BlackPawn;
8618                  p = PieceToNumber((ChessSquare)p);
8619              if(p >= gameInfo.holdingsSize) p = 0;
8620              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8621                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8622              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8623                         board[BOARD_HEIGHT-1-p][1] = 0;
8624         }
8625       }
8626       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8627           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8628         /* [HGM] holdings: Add to holdings, if holdings exist */
8629         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8630                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8631                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8632         }
8633         p = (int) captured;
8634         if (p >= (int) BlackPawn) {
8635           p -= (int)BlackPawn;
8636           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8637                   /* in Shogi restore piece to its original  first */
8638                   captured = (ChessSquare) (DEMOTED captured);
8639                   p = DEMOTED p;
8640           }
8641           p = PieceToNumber((ChessSquare)p);
8642           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8643           board[p][BOARD_WIDTH-2]++;
8644           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8645         } else {
8646           p -= (int)WhitePawn;
8647           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8648                   captured = (ChessSquare) (DEMOTED captured);
8649                   p = DEMOTED p;
8650           }
8651           p = PieceToNumber((ChessSquare)p);
8652           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8653           board[BOARD_HEIGHT-1-p][1]++;
8654           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8655         }
8656       }
8657     } else if (gameInfo.variant == VariantAtomic) {
8658       if (captured != EmptySquare) {
8659         int y, x;
8660         for (y = toY-1; y <= toY+1; y++) {
8661           for (x = toX-1; x <= toX+1; x++) {
8662             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8663                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8664               board[y][x] = EmptySquare;
8665             }
8666           }
8667         }
8668         board[toY][toX] = EmptySquare;
8669       }
8670     }
8671     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8672         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8673     } else
8674     if(promoChar == '+') {
8675         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8676         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8677     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8678         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8679     }
8680     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8681                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8682         // [HGM] superchess: take promotion piece out of holdings
8683         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8684         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8685             if(!--board[k][BOARD_WIDTH-2])
8686                 board[k][BOARD_WIDTH-1] = EmptySquare;
8687         } else {
8688             if(!--board[BOARD_HEIGHT-1-k][1])
8689                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8690         }
8691     }
8692
8693 }
8694
8695 /* Updates forwardMostMove */
8696 void
8697 MakeMove(fromX, fromY, toX, toY, promoChar)
8698      int fromX, fromY, toX, toY;
8699      int promoChar;
8700 {
8701 //    forwardMostMove++; // [HGM] bare: moved downstream
8702
8703     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8704         int timeLeft; static int lastLoadFlag=0; int king, piece;
8705         piece = boards[forwardMostMove][fromY][fromX];
8706         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8707         if(gameInfo.variant == VariantKnightmate)
8708             king += (int) WhiteUnicorn - (int) WhiteKing;
8709         if(forwardMostMove == 0) {
8710             if(blackPlaysFirst)
8711                 fprintf(serverMoves, "%s;", second.tidy);
8712             fprintf(serverMoves, "%s;", first.tidy);
8713             if(!blackPlaysFirst)
8714                 fprintf(serverMoves, "%s;", second.tidy);
8715         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8716         lastLoadFlag = loadFlag;
8717         // print base move
8718         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8719         // print castling suffix
8720         if( toY == fromY && piece == king ) {
8721             if(toX-fromX > 1)
8722                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8723             if(fromX-toX >1)
8724                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8725         }
8726         // e.p. suffix
8727         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8728              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8729              boards[forwardMostMove][toY][toX] == EmptySquare
8730              && fromX != toX && fromY != toY)
8731                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8732         // promotion suffix
8733         if(promoChar != NULLCHAR)
8734                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8735         if(!loadFlag) {
8736             fprintf(serverMoves, "/%d/%d",
8737                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8738             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8739             else                      timeLeft = blackTimeRemaining/1000;
8740             fprintf(serverMoves, "/%d", timeLeft);
8741         }
8742         fflush(serverMoves);
8743     }
8744
8745     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8746       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8747                         0, 1);
8748       return;
8749     }
8750     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8751     if (commentList[forwardMostMove+1] != NULL) {
8752         free(commentList[forwardMostMove+1]);
8753         commentList[forwardMostMove+1] = NULL;
8754     }
8755     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8756     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8757     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8758     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8759     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8760     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8761     gameInfo.result = GameUnfinished;
8762     if (gameInfo.resultDetails != NULL) {
8763         free(gameInfo.resultDetails);
8764         gameInfo.resultDetails = NULL;
8765     }
8766     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8767                               moveList[forwardMostMove - 1]);
8768     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8769                              PosFlags(forwardMostMove - 1),
8770                              fromY, fromX, toY, toX, promoChar,
8771                              parseList[forwardMostMove - 1]);
8772     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8773       case MT_NONE:
8774       case MT_STALEMATE:
8775       default:
8776         break;
8777       case MT_CHECK:
8778         if(gameInfo.variant != VariantShogi)
8779             strcat(parseList[forwardMostMove - 1], "+");
8780         break;
8781       case MT_CHECKMATE:
8782       case MT_STAINMATE:
8783         strcat(parseList[forwardMostMove - 1], "#");
8784         break;
8785     }
8786     if (appData.debugMode) {
8787         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8788     }
8789
8790 }
8791
8792 /* Updates currentMove if not pausing */
8793 void
8794 ShowMove(fromX, fromY, toX, toY)
8795 {
8796     int instant = (gameMode == PlayFromGameFile) ?
8797         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8798     if(appData.noGUI) return;
8799     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8800         if (!instant) {
8801             if (forwardMostMove == currentMove + 1) {
8802                 AnimateMove(boards[forwardMostMove - 1],
8803                             fromX, fromY, toX, toY);
8804             }
8805             if (appData.highlightLastMove) {
8806                 SetHighlights(fromX, fromY, toX, toY);
8807             }
8808         }
8809         currentMove = forwardMostMove;
8810     }
8811
8812     if (instant) return;
8813
8814     DisplayMove(currentMove - 1);
8815     DrawPosition(FALSE, boards[currentMove]);
8816     DisplayBothClocks();
8817     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8818 }
8819
8820 void SendEgtPath(ChessProgramState *cps)
8821 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8822         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8823
8824         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8825
8826         while(*p) {
8827             char c, *q = name+1, *r, *s;
8828
8829             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8830             while(*p && *p != ',') *q++ = *p++;
8831             *q++ = ':'; *q = 0;
8832             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8833                 strcmp(name, ",nalimov:") == 0 ) {
8834                 // take nalimov path from the menu-changeable option first, if it is defined
8835               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8836                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8837             } else
8838             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8839                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8840                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8841                 s = r = StrStr(s, ":") + 1; // beginning of path info
8842                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8843                 c = *r; *r = 0;             // temporarily null-terminate path info
8844                     *--q = 0;               // strip of trailig ':' from name
8845                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8846                 *r = c;
8847                 SendToProgram(buf,cps);     // send egtbpath command for this format
8848             }
8849             if(*p == ',') p++; // read away comma to position for next format name
8850         }
8851 }
8852
8853 void
8854 InitChessProgram(cps, setup)
8855      ChessProgramState *cps;
8856      int setup; /* [HGM] needed to setup FRC opening position */
8857 {
8858     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8859     if (appData.noChessProgram) return;
8860     hintRequested = FALSE;
8861     bookRequested = FALSE;
8862
8863     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8864     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8865     if(cps->memSize) { /* [HGM] memory */
8866       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8867         SendToProgram(buf, cps);
8868     }
8869     SendEgtPath(cps); /* [HGM] EGT */
8870     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8871       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8872         SendToProgram(buf, cps);
8873     }
8874
8875     SendToProgram(cps->initString, cps);
8876     if (gameInfo.variant != VariantNormal &&
8877         gameInfo.variant != VariantLoadable
8878         /* [HGM] also send variant if board size non-standard */
8879         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8880                                             ) {
8881       char *v = VariantName(gameInfo.variant);
8882       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8883         /* [HGM] in protocol 1 we have to assume all variants valid */
8884         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8885         DisplayFatalError(buf, 0, 1);
8886         return;
8887       }
8888
8889       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8890       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8891       if( gameInfo.variant == VariantXiangqi )
8892            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8893       if( gameInfo.variant == VariantShogi )
8894            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8895       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8896            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8897       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8898                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8899            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8900       if( gameInfo.variant == VariantCourier )
8901            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8902       if( gameInfo.variant == VariantSuper )
8903            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8904       if( gameInfo.variant == VariantGreat )
8905            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8906       if( gameInfo.variant == VariantSChess )
8907            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
8908
8909       if(overruled) {
8910         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8911                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8912            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8913            if(StrStr(cps->variants, b) == NULL) {
8914                // specific sized variant not known, check if general sizing allowed
8915                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8916                    if(StrStr(cps->variants, "boardsize") == NULL) {
8917                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8918                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8919                        DisplayFatalError(buf, 0, 1);
8920                        return;
8921                    }
8922                    /* [HGM] here we really should compare with the maximum supported board size */
8923                }
8924            }
8925       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8926       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8927       SendToProgram(buf, cps);
8928     }
8929     currentlyInitializedVariant = gameInfo.variant;
8930
8931     /* [HGM] send opening position in FRC to first engine */
8932     if(setup) {
8933           SendToProgram("force\n", cps);
8934           SendBoard(cps, 0);
8935           /* engine is now in force mode! Set flag to wake it up after first move. */
8936           setboardSpoiledMachineBlack = 1;
8937     }
8938
8939     if (cps->sendICS) {
8940       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8941       SendToProgram(buf, cps);
8942     }
8943     cps->maybeThinking = FALSE;
8944     cps->offeredDraw = 0;
8945     if (!appData.icsActive) {
8946         SendTimeControl(cps, movesPerSession, timeControl,
8947                         timeIncrement, appData.searchDepth,
8948                         searchTime);
8949     }
8950     if (appData.showThinking
8951         // [HGM] thinking: four options require thinking output to be sent
8952         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8953                                 ) {
8954         SendToProgram("post\n", cps);
8955     }
8956     SendToProgram("hard\n", cps);
8957     if (!appData.ponderNextMove) {
8958         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8959            it without being sure what state we are in first.  "hard"
8960            is not a toggle, so that one is OK.
8961          */
8962         SendToProgram("easy\n", cps);
8963     }
8964     if (cps->usePing) {
8965       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8966       SendToProgram(buf, cps);
8967     }
8968     cps->initDone = TRUE;
8969 }
8970
8971
8972 void
8973 StartChessProgram(cps)
8974      ChessProgramState *cps;
8975 {
8976     char buf[MSG_SIZ];
8977     int err;
8978
8979     if (appData.noChessProgram) return;
8980     cps->initDone = FALSE;
8981
8982     if (strcmp(cps->host, "localhost") == 0) {
8983         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8984     } else if (*appData.remoteShell == NULLCHAR) {
8985         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8986     } else {
8987         if (*appData.remoteUser == NULLCHAR) {
8988           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8989                     cps->program);
8990         } else {
8991           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8992                     cps->host, appData.remoteUser, cps->program);
8993         }
8994         err = StartChildProcess(buf, "", &cps->pr);
8995     }
8996
8997     if (err != 0) {
8998       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8999         DisplayFatalError(buf, err, 1);
9000         cps->pr = NoProc;
9001         cps->isr = NULL;
9002         return;
9003     }
9004
9005     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9006     if (cps->protocolVersion > 1) {
9007       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9008       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9009       cps->comboCnt = 0;  //                and values of combo boxes
9010       SendToProgram(buf, cps);
9011     } else {
9012       SendToProgram("xboard\n", cps);
9013     }
9014 }
9015
9016
9017 void
9018 TwoMachinesEventIfReady P((void))
9019 {
9020   if (first.lastPing != first.lastPong) {
9021     DisplayMessage("", _("Waiting for first chess program"));
9022     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9023     return;
9024   }
9025   if (second.lastPing != second.lastPong) {
9026     DisplayMessage("", _("Waiting for second chess program"));
9027     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9028     return;
9029   }
9030   ThawUI();
9031   TwoMachinesEvent();
9032 }
9033
9034 void
9035 NextMatchGame P((void))
9036 {
9037     int index; /* [HGM] autoinc: step load index during match */
9038     Reset(FALSE, TRUE);
9039     if (*appData.loadGameFile != NULLCHAR) {
9040         index = appData.loadGameIndex;
9041         if(index < 0) { // [HGM] autoinc
9042             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9043             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9044         }
9045         LoadGameFromFile(appData.loadGameFile,
9046                          index,
9047                          appData.loadGameFile, FALSE);
9048     } else if (*appData.loadPositionFile != NULLCHAR) {
9049         index = appData.loadPositionIndex;
9050         if(index < 0) { // [HGM] autoinc
9051             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9052             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9053         }
9054         LoadPositionFromFile(appData.loadPositionFile,
9055                              index,
9056                              appData.loadPositionFile);
9057     }
9058     TwoMachinesEventIfReady();
9059 }
9060
9061 void UserAdjudicationEvent( int result )
9062 {
9063     ChessMove gameResult = GameIsDrawn;
9064
9065     if( result > 0 ) {
9066         gameResult = WhiteWins;
9067     }
9068     else if( result < 0 ) {
9069         gameResult = BlackWins;
9070     }
9071
9072     if( gameMode == TwoMachinesPlay ) {
9073         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9074     }
9075 }
9076
9077
9078 // [HGM] save: calculate checksum of game to make games easily identifiable
9079 int StringCheckSum(char *s)
9080 {
9081         int i = 0;
9082         if(s==NULL) return 0;
9083         while(*s) i = i*259 + *s++;
9084         return i;
9085 }
9086
9087 int GameCheckSum()
9088 {
9089         int i, sum=0;
9090         for(i=backwardMostMove; i<forwardMostMove; i++) {
9091                 sum += pvInfoList[i].depth;
9092                 sum += StringCheckSum(parseList[i]);
9093                 sum += StringCheckSum(commentList[i]);
9094                 sum *= 261;
9095         }
9096         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9097         return sum + StringCheckSum(commentList[i]);
9098 } // end of save patch
9099
9100 void
9101 GameEnds(result, resultDetails, whosays)
9102      ChessMove result;
9103      char *resultDetails;
9104      int whosays;
9105 {
9106     GameMode nextGameMode;
9107     int isIcsGame;
9108     char buf[MSG_SIZ], popupRequested = 0;
9109
9110     if(endingGame) return; /* [HGM] crash: forbid recursion */
9111     endingGame = 1;
9112     if(twoBoards) { // [HGM] dual: switch back to one board
9113         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9114         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9115     }
9116     if (appData.debugMode) {
9117       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9118               result, resultDetails ? resultDetails : "(null)", whosays);
9119     }
9120
9121     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9122
9123     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9124         /* If we are playing on ICS, the server decides when the
9125            game is over, but the engine can offer to draw, claim
9126            a draw, or resign.
9127          */
9128 #if ZIPPY
9129         if (appData.zippyPlay && first.initDone) {
9130             if (result == GameIsDrawn) {
9131                 /* In case draw still needs to be claimed */
9132                 SendToICS(ics_prefix);
9133                 SendToICS("draw\n");
9134             } else if (StrCaseStr(resultDetails, "resign")) {
9135                 SendToICS(ics_prefix);
9136                 SendToICS("resign\n");
9137             }
9138         }
9139 #endif
9140         endingGame = 0; /* [HGM] crash */
9141         return;
9142     }
9143
9144     /* If we're loading the game from a file, stop */
9145     if (whosays == GE_FILE) {
9146       (void) StopLoadGameTimer();
9147       gameFileFP = NULL;
9148     }
9149
9150     /* Cancel draw offers */
9151     first.offeredDraw = second.offeredDraw = 0;
9152
9153     /* If this is an ICS game, only ICS can really say it's done;
9154        if not, anyone can. */
9155     isIcsGame = (gameMode == IcsPlayingWhite ||
9156                  gameMode == IcsPlayingBlack ||
9157                  gameMode == IcsObserving    ||
9158                  gameMode == IcsExamining);
9159
9160     if (!isIcsGame || whosays == GE_ICS) {
9161         /* OK -- not an ICS game, or ICS said it was done */
9162         StopClocks();
9163         if (!isIcsGame && !appData.noChessProgram)
9164           SetUserThinkingEnables();
9165
9166         /* [HGM] if a machine claims the game end we verify this claim */
9167         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9168             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9169                 char claimer;
9170                 ChessMove trueResult = (ChessMove) -1;
9171
9172                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9173                                             first.twoMachinesColor[0] :
9174                                             second.twoMachinesColor[0] ;
9175
9176                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9177                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9178                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9179                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9180                 } else
9181                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9182                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9183                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9184                 } else
9185                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9186                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9187                 }
9188
9189                 // now verify win claims, but not in drop games, as we don't understand those yet
9190                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9191                                                  || gameInfo.variant == VariantGreat) &&
9192                     (result == WhiteWins && claimer == 'w' ||
9193                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9194                       if (appData.debugMode) {
9195                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9196                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9197                       }
9198                       if(result != trueResult) {
9199                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9200                               result = claimer == 'w' ? BlackWins : WhiteWins;
9201                               resultDetails = buf;
9202                       }
9203                 } else
9204                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9205                     && (forwardMostMove <= backwardMostMove ||
9206                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9207                         (claimer=='b')==(forwardMostMove&1))
9208                                                                                   ) {
9209                       /* [HGM] verify: draws that were not flagged are false claims */
9210                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9211                       result = claimer == 'w' ? BlackWins : WhiteWins;
9212                       resultDetails = buf;
9213                 }
9214                 /* (Claiming a loss is accepted no questions asked!) */
9215             }
9216             /* [HGM] bare: don't allow bare King to win */
9217             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9218                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9219                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9220                && result != GameIsDrawn)
9221             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9222                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9223                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9224                         if(p >= 0 && p <= (int)WhiteKing) k++;
9225                 }
9226                 if (appData.debugMode) {
9227                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9228                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9229                 }
9230                 if(k <= 1) {
9231                         result = GameIsDrawn;
9232                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9233                         resultDetails = buf;
9234                 }
9235             }
9236         }
9237
9238
9239         if(serverMoves != NULL && !loadFlag) { char c = '=';
9240             if(result==WhiteWins) c = '+';
9241             if(result==BlackWins) c = '-';
9242             if(resultDetails != NULL)
9243                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9244         }
9245         if (resultDetails != NULL) {
9246             gameInfo.result = result;
9247             gameInfo.resultDetails = StrSave(resultDetails);
9248
9249             /* display last move only if game was not loaded from file */
9250             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9251                 DisplayMove(currentMove - 1);
9252
9253             if (forwardMostMove != 0) {
9254                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9255                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9256                                                                 ) {
9257                     if (*appData.saveGameFile != NULLCHAR) {
9258                         SaveGameToFile(appData.saveGameFile, TRUE);
9259                     } else if (appData.autoSaveGames) {
9260                         AutoSaveGame();
9261                     }
9262                     if (*appData.savePositionFile != NULLCHAR) {
9263                         SavePositionToFile(appData.savePositionFile);
9264                     }
9265                 }
9266             }
9267
9268             /* Tell program how game ended in case it is learning */
9269             /* [HGM] Moved this to after saving the PGN, just in case */
9270             /* engine died and we got here through time loss. In that */
9271             /* case we will get a fatal error writing the pipe, which */
9272             /* would otherwise lose us the PGN.                       */
9273             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9274             /* output during GameEnds should never be fatal anymore   */
9275             if (gameMode == MachinePlaysWhite ||
9276                 gameMode == MachinePlaysBlack ||
9277                 gameMode == TwoMachinesPlay ||
9278                 gameMode == IcsPlayingWhite ||
9279                 gameMode == IcsPlayingBlack ||
9280                 gameMode == BeginningOfGame) {
9281                 char buf[MSG_SIZ];
9282                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9283                         resultDetails);
9284                 if (first.pr != NoProc) {
9285                     SendToProgram(buf, &first);
9286                 }
9287                 if (second.pr != NoProc &&
9288                     gameMode == TwoMachinesPlay) {
9289                     SendToProgram(buf, &second);
9290                 }
9291             }
9292         }
9293
9294         if (appData.icsActive) {
9295             if (appData.quietPlay &&
9296                 (gameMode == IcsPlayingWhite ||
9297                  gameMode == IcsPlayingBlack)) {
9298                 SendToICS(ics_prefix);
9299                 SendToICS("set shout 1\n");
9300             }
9301             nextGameMode = IcsIdle;
9302             ics_user_moved = FALSE;
9303             /* clean up premove.  It's ugly when the game has ended and the
9304              * premove highlights are still on the board.
9305              */
9306             if (gotPremove) {
9307               gotPremove = FALSE;
9308               ClearPremoveHighlights();
9309               DrawPosition(FALSE, boards[currentMove]);
9310             }
9311             if (whosays == GE_ICS) {
9312                 switch (result) {
9313                 case WhiteWins:
9314                     if (gameMode == IcsPlayingWhite)
9315                         PlayIcsWinSound();
9316                     else if(gameMode == IcsPlayingBlack)
9317                         PlayIcsLossSound();
9318                     break;
9319                 case BlackWins:
9320                     if (gameMode == IcsPlayingBlack)
9321                         PlayIcsWinSound();
9322                     else if(gameMode == IcsPlayingWhite)
9323                         PlayIcsLossSound();
9324                     break;
9325                 case GameIsDrawn:
9326                     PlayIcsDrawSound();
9327                     break;
9328                 default:
9329                     PlayIcsUnfinishedSound();
9330                 }
9331             }
9332         } else if (gameMode == EditGame ||
9333                    gameMode == PlayFromGameFile ||
9334                    gameMode == AnalyzeMode ||
9335                    gameMode == AnalyzeFile) {
9336             nextGameMode = gameMode;
9337         } else {
9338             nextGameMode = EndOfGame;
9339         }
9340         pausing = FALSE;
9341         ModeHighlight();
9342     } else {
9343         nextGameMode = gameMode;
9344     }
9345
9346     if (appData.noChessProgram) {
9347         gameMode = nextGameMode;
9348         ModeHighlight();
9349         endingGame = 0; /* [HGM] crash */
9350         return;
9351     }
9352
9353     if (first.reuse) {
9354         /* Put first chess program into idle state */
9355         if (first.pr != NoProc &&
9356             (gameMode == MachinePlaysWhite ||
9357              gameMode == MachinePlaysBlack ||
9358              gameMode == TwoMachinesPlay ||
9359              gameMode == IcsPlayingWhite ||
9360              gameMode == IcsPlayingBlack ||
9361              gameMode == BeginningOfGame)) {
9362             SendToProgram("force\n", &first);
9363             if (first.usePing) {
9364               char buf[MSG_SIZ];
9365               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9366               SendToProgram(buf, &first);
9367             }
9368         }
9369     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9370         /* Kill off first chess program */
9371         if (first.isr != NULL)
9372           RemoveInputSource(first.isr);
9373         first.isr = NULL;
9374
9375         if (first.pr != NoProc) {
9376             ExitAnalyzeMode();
9377             DoSleep( appData.delayBeforeQuit );
9378             SendToProgram("quit\n", &first);
9379             DoSleep( appData.delayAfterQuit );
9380             DestroyChildProcess(first.pr, first.useSigterm);
9381         }
9382         first.pr = NoProc;
9383     }
9384     if (second.reuse) {
9385         /* Put second chess program into idle state */
9386         if (second.pr != NoProc &&
9387             gameMode == TwoMachinesPlay) {
9388             SendToProgram("force\n", &second);
9389             if (second.usePing) {
9390               char buf[MSG_SIZ];
9391               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9392               SendToProgram(buf, &second);
9393             }
9394         }
9395     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9396         /* Kill off second chess program */
9397         if (second.isr != NULL)
9398           RemoveInputSource(second.isr);
9399         second.isr = NULL;
9400
9401         if (second.pr != NoProc) {
9402             DoSleep( appData.delayBeforeQuit );
9403             SendToProgram("quit\n", &second);
9404             DoSleep( appData.delayAfterQuit );
9405             DestroyChildProcess(second.pr, second.useSigterm);
9406         }
9407         second.pr = NoProc;
9408     }
9409
9410     if (matchMode && gameMode == TwoMachinesPlay) {
9411         switch (result) {
9412         case WhiteWins:
9413           if (first.twoMachinesColor[0] == 'w') {
9414             first.matchWins++;
9415           } else {
9416             second.matchWins++;
9417           }
9418           break;
9419         case BlackWins:
9420           if (first.twoMachinesColor[0] == 'b') {
9421             first.matchWins++;
9422           } else {
9423             second.matchWins++;
9424           }
9425           break;
9426         default:
9427           break;
9428         }
9429         if (matchGame < appData.matchGames) {
9430             char *tmp;
9431             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9432                 tmp = first.twoMachinesColor;
9433                 first.twoMachinesColor = second.twoMachinesColor;
9434                 second.twoMachinesColor = tmp;
9435             }
9436             gameMode = nextGameMode;
9437             matchGame++;
9438             if(appData.matchPause>10000 || appData.matchPause<10)
9439                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9440             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9441             endingGame = 0; /* [HGM] crash */
9442             return;
9443         } else {
9444             gameMode = nextGameMode;
9445             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9446                      first.tidy, second.tidy,
9447                      first.matchWins, second.matchWins,
9448                      appData.matchGames - (first.matchWins + second.matchWins));
9449             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9450         }
9451     }
9452     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9453         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9454       ExitAnalyzeMode();
9455     gameMode = nextGameMode;
9456     ModeHighlight();
9457     endingGame = 0;  /* [HGM] crash */
9458     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9459       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9460         matchMode = FALSE; appData.matchGames = matchGame = 0;
9461         DisplayNote(buf);
9462       }
9463     }
9464 }
9465
9466 /* Assumes program was just initialized (initString sent).
9467    Leaves program in force mode. */
9468 void
9469 FeedMovesToProgram(cps, upto)
9470      ChessProgramState *cps;
9471      int upto;
9472 {
9473     int i;
9474
9475     if (appData.debugMode)
9476       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9477               startedFromSetupPosition ? "position and " : "",
9478               backwardMostMove, upto, cps->which);
9479     if(currentlyInitializedVariant != gameInfo.variant) {
9480       char buf[MSG_SIZ];
9481         // [HGM] variantswitch: make engine aware of new variant
9482         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9483                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9484         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9485         SendToProgram(buf, cps);
9486         currentlyInitializedVariant = gameInfo.variant;
9487     }
9488     SendToProgram("force\n", cps);
9489     if (startedFromSetupPosition) {
9490         SendBoard(cps, backwardMostMove);
9491     if (appData.debugMode) {
9492         fprintf(debugFP, "feedMoves\n");
9493     }
9494     }
9495     for (i = backwardMostMove; i < upto; i++) {
9496         SendMoveToProgram(i, cps);
9497     }
9498 }
9499
9500
9501 void
9502 ResurrectChessProgram()
9503 {
9504      /* The chess program may have exited.
9505         If so, restart it and feed it all the moves made so far. */
9506
9507     if (appData.noChessProgram || first.pr != NoProc) return;
9508
9509     StartChessProgram(&first);
9510     InitChessProgram(&first, FALSE);
9511     FeedMovesToProgram(&first, currentMove);
9512
9513     if (!first.sendTime) {
9514         /* can't tell gnuchess what its clock should read,
9515            so we bow to its notion. */
9516         ResetClocks();
9517         timeRemaining[0][currentMove] = whiteTimeRemaining;
9518         timeRemaining[1][currentMove] = blackTimeRemaining;
9519     }
9520
9521     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9522                 appData.icsEngineAnalyze) && first.analysisSupport) {
9523       SendToProgram("analyze\n", &first);
9524       first.analyzing = TRUE;
9525     }
9526 }
9527
9528 /*
9529  * Button procedures
9530  */
9531 void
9532 Reset(redraw, init)
9533      int redraw, init;
9534 {
9535     int i;
9536
9537     if (appData.debugMode) {
9538         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9539                 redraw, init, gameMode);
9540     }
9541     CleanupTail(); // [HGM] vari: delete any stored variations
9542     pausing = pauseExamInvalid = FALSE;
9543     startedFromSetupPosition = blackPlaysFirst = FALSE;
9544     firstMove = TRUE;
9545     whiteFlag = blackFlag = FALSE;
9546     userOfferedDraw = FALSE;
9547     hintRequested = bookRequested = FALSE;
9548     first.maybeThinking = FALSE;
9549     second.maybeThinking = FALSE;
9550     first.bookSuspend = FALSE; // [HGM] book
9551     second.bookSuspend = FALSE;
9552     thinkOutput[0] = NULLCHAR;
9553     lastHint[0] = NULLCHAR;
9554     ClearGameInfo(&gameInfo);
9555     gameInfo.variant = StringToVariant(appData.variant);
9556     ics_user_moved = ics_clock_paused = FALSE;
9557     ics_getting_history = H_FALSE;
9558     ics_gamenum = -1;
9559     white_holding[0] = black_holding[0] = NULLCHAR;
9560     ClearProgramStats();
9561     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9562
9563     ResetFrontEnd();
9564     ClearHighlights();
9565     flipView = appData.flipView;
9566     ClearPremoveHighlights();
9567     gotPremove = FALSE;
9568     alarmSounded = FALSE;
9569
9570     GameEnds(EndOfFile, NULL, GE_PLAYER);
9571     if(appData.serverMovesName != NULL) {
9572         /* [HGM] prepare to make moves file for broadcasting */
9573         clock_t t = clock();
9574         if(serverMoves != NULL) fclose(serverMoves);
9575         serverMoves = fopen(appData.serverMovesName, "r");
9576         if(serverMoves != NULL) {
9577             fclose(serverMoves);
9578             /* delay 15 sec before overwriting, so all clients can see end */
9579             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9580         }
9581         serverMoves = fopen(appData.serverMovesName, "w");
9582     }
9583
9584     ExitAnalyzeMode();
9585     gameMode = BeginningOfGame;
9586     ModeHighlight();
9587     if(appData.icsActive) gameInfo.variant = VariantNormal;
9588     currentMove = forwardMostMove = backwardMostMove = 0;
9589     InitPosition(redraw);
9590     for (i = 0; i < MAX_MOVES; i++) {
9591         if (commentList[i] != NULL) {
9592             free(commentList[i]);
9593             commentList[i] = NULL;
9594         }
9595     }
9596     ResetClocks();
9597     timeRemaining[0][0] = whiteTimeRemaining;
9598     timeRemaining[1][0] = blackTimeRemaining;
9599     if (first.pr == NULL) {
9600         StartChessProgram(&first);
9601     }
9602     if (init) {
9603             InitChessProgram(&first, startedFromSetupPosition);
9604     }
9605     DisplayTitle("");
9606     DisplayMessage("", "");
9607     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9608     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9609 }
9610
9611 void
9612 AutoPlayGameLoop()
9613 {
9614     for (;;) {
9615         if (!AutoPlayOneMove())
9616           return;
9617         if (matchMode || appData.timeDelay == 0)
9618           continue;
9619         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9620           return;
9621         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9622         break;
9623     }
9624 }
9625
9626
9627 int
9628 AutoPlayOneMove()
9629 {
9630     int fromX, fromY, toX, toY;
9631
9632     if (appData.debugMode) {
9633       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9634     }
9635
9636     if (gameMode != PlayFromGameFile)
9637       return FALSE;
9638
9639     if (currentMove >= forwardMostMove) {
9640       gameMode = EditGame;
9641       ModeHighlight();
9642
9643       /* [AS] Clear current move marker at the end of a game */
9644       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9645
9646       return FALSE;
9647     }
9648
9649     toX = moveList[currentMove][2] - AAA;
9650     toY = moveList[currentMove][3] - ONE;
9651
9652     if (moveList[currentMove][1] == '@') {
9653         if (appData.highlightLastMove) {
9654             SetHighlights(-1, -1, toX, toY);
9655         }
9656     } else {
9657         fromX = moveList[currentMove][0] - AAA;
9658         fromY = moveList[currentMove][1] - ONE;
9659
9660         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9661
9662         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9663
9664         if (appData.highlightLastMove) {
9665             SetHighlights(fromX, fromY, toX, toY);
9666         }
9667     }
9668     DisplayMove(currentMove);
9669     SendMoveToProgram(currentMove++, &first);
9670     DisplayBothClocks();
9671     DrawPosition(FALSE, boards[currentMove]);
9672     // [HGM] PV info: always display, routine tests if empty
9673     DisplayComment(currentMove - 1, commentList[currentMove]);
9674     return TRUE;
9675 }
9676
9677
9678 int
9679 LoadGameOneMove(readAhead)
9680      ChessMove readAhead;
9681 {
9682     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9683     char promoChar = NULLCHAR;
9684     ChessMove moveType;
9685     char move[MSG_SIZ];
9686     char *p, *q;
9687
9688     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9689         gameMode != AnalyzeMode && gameMode != Training) {
9690         gameFileFP = NULL;
9691         return FALSE;
9692     }
9693
9694     yyboardindex = forwardMostMove;
9695     if (readAhead != EndOfFile) {
9696       moveType = readAhead;
9697     } else {
9698       if (gameFileFP == NULL)
9699           return FALSE;
9700       moveType = (ChessMove) Myylex();
9701     }
9702
9703     done = FALSE;
9704     switch (moveType) {
9705       case Comment:
9706         if (appData.debugMode)
9707           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9708         p = yy_text;
9709
9710         /* append the comment but don't display it */
9711         AppendComment(currentMove, p, FALSE);
9712         return TRUE;
9713
9714       case WhiteCapturesEnPassant:
9715       case BlackCapturesEnPassant:
9716       case WhitePromotion:
9717       case BlackPromotion:
9718       case WhiteNonPromotion:
9719       case BlackNonPromotion:
9720       case NormalMove:
9721       case WhiteKingSideCastle:
9722       case WhiteQueenSideCastle:
9723       case BlackKingSideCastle:
9724       case BlackQueenSideCastle:
9725       case WhiteKingSideCastleWild:
9726       case WhiteQueenSideCastleWild:
9727       case BlackKingSideCastleWild:
9728       case BlackQueenSideCastleWild:
9729       /* PUSH Fabien */
9730       case WhiteHSideCastleFR:
9731       case WhiteASideCastleFR:
9732       case BlackHSideCastleFR:
9733       case BlackASideCastleFR:
9734       /* POP Fabien */
9735         if (appData.debugMode)
9736           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9737         fromX = currentMoveString[0] - AAA;
9738         fromY = currentMoveString[1] - ONE;
9739         toX = currentMoveString[2] - AAA;
9740         toY = currentMoveString[3] - ONE;
9741         promoChar = currentMoveString[4];
9742         break;
9743
9744       case WhiteDrop:
9745       case BlackDrop:
9746         if (appData.debugMode)
9747           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9748         fromX = moveType == WhiteDrop ?
9749           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9750         (int) CharToPiece(ToLower(currentMoveString[0]));
9751         fromY = DROP_RANK;
9752         toX = currentMoveString[2] - AAA;
9753         toY = currentMoveString[3] - ONE;
9754         break;
9755
9756       case WhiteWins:
9757       case BlackWins:
9758       case GameIsDrawn:
9759       case GameUnfinished:
9760         if (appData.debugMode)
9761           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9762         p = strchr(yy_text, '{');
9763         if (p == NULL) p = strchr(yy_text, '(');
9764         if (p == NULL) {
9765             p = yy_text;
9766             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9767         } else {
9768             q = strchr(p, *p == '{' ? '}' : ')');
9769             if (q != NULL) *q = NULLCHAR;
9770             p++;
9771         }
9772         GameEnds(moveType, p, GE_FILE);
9773         done = TRUE;
9774         if (cmailMsgLoaded) {
9775             ClearHighlights();
9776             flipView = WhiteOnMove(currentMove);
9777             if (moveType == GameUnfinished) flipView = !flipView;
9778             if (appData.debugMode)
9779               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9780         }
9781         break;
9782
9783       case EndOfFile:
9784         if (appData.debugMode)
9785           fprintf(debugFP, "Parser hit end of file\n");
9786         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9787           case MT_NONE:
9788           case MT_CHECK:
9789             break;
9790           case MT_CHECKMATE:
9791           case MT_STAINMATE:
9792             if (WhiteOnMove(currentMove)) {
9793                 GameEnds(BlackWins, "Black mates", GE_FILE);
9794             } else {
9795                 GameEnds(WhiteWins, "White mates", GE_FILE);
9796             }
9797             break;
9798           case MT_STALEMATE:
9799             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9800             break;
9801         }
9802         done = TRUE;
9803         break;
9804
9805       case MoveNumberOne:
9806         if (lastLoadGameStart == GNUChessGame) {
9807             /* GNUChessGames have numbers, but they aren't move numbers */
9808             if (appData.debugMode)
9809               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9810                       yy_text, (int) moveType);
9811             return LoadGameOneMove(EndOfFile); /* tail recursion */
9812         }
9813         /* else fall thru */
9814
9815       case XBoardGame:
9816       case GNUChessGame:
9817       case PGNTag:
9818         /* Reached start of next game in file */
9819         if (appData.debugMode)
9820           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9821         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9822           case MT_NONE:
9823           case MT_CHECK:
9824             break;
9825           case MT_CHECKMATE:
9826           case MT_STAINMATE:
9827             if (WhiteOnMove(currentMove)) {
9828                 GameEnds(BlackWins, "Black mates", GE_FILE);
9829             } else {
9830                 GameEnds(WhiteWins, "White mates", GE_FILE);
9831             }
9832             break;
9833           case MT_STALEMATE:
9834             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9835             break;
9836         }
9837         done = TRUE;
9838         break;
9839
9840       case PositionDiagram:     /* should not happen; ignore */
9841       case ElapsedTime:         /* ignore */
9842       case NAG:                 /* ignore */
9843         if (appData.debugMode)
9844           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9845                   yy_text, (int) moveType);
9846         return LoadGameOneMove(EndOfFile); /* tail recursion */
9847
9848       case IllegalMove:
9849         if (appData.testLegality) {
9850             if (appData.debugMode)
9851               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9852             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9853                     (forwardMostMove / 2) + 1,
9854                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9855             DisplayError(move, 0);
9856             done = TRUE;
9857         } else {
9858             if (appData.debugMode)
9859               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9860                       yy_text, currentMoveString);
9861             fromX = currentMoveString[0] - AAA;
9862             fromY = currentMoveString[1] - ONE;
9863             toX = currentMoveString[2] - AAA;
9864             toY = currentMoveString[3] - ONE;
9865             promoChar = currentMoveString[4];
9866         }
9867         break;
9868
9869       case AmbiguousMove:
9870         if (appData.debugMode)
9871           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9872         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9873                 (forwardMostMove / 2) + 1,
9874                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9875         DisplayError(move, 0);
9876         done = TRUE;
9877         break;
9878
9879       default:
9880       case ImpossibleMove:
9881         if (appData.debugMode)
9882           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9883         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9884                 (forwardMostMove / 2) + 1,
9885                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9886         DisplayError(move, 0);
9887         done = TRUE;
9888         break;
9889     }
9890
9891     if (done) {
9892         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9893             DrawPosition(FALSE, boards[currentMove]);
9894             DisplayBothClocks();
9895             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9896               DisplayComment(currentMove - 1, commentList[currentMove]);
9897         }
9898         (void) StopLoadGameTimer();
9899         gameFileFP = NULL;
9900         cmailOldMove = forwardMostMove;
9901         return FALSE;
9902     } else {
9903         /* currentMoveString is set as a side-effect of yylex */
9904         strcat(currentMoveString, "\n");
9905         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9906
9907         thinkOutput[0] = NULLCHAR;
9908         MakeMove(fromX, fromY, toX, toY, promoChar);
9909         currentMove = forwardMostMove;
9910         return TRUE;
9911     }
9912 }
9913
9914 /* Load the nth game from the given file */
9915 int
9916 LoadGameFromFile(filename, n, title, useList)
9917      char *filename;
9918      int n;
9919      char *title;
9920      /*Boolean*/ int useList;
9921 {
9922     FILE *f;
9923     char buf[MSG_SIZ];
9924
9925     if (strcmp(filename, "-") == 0) {
9926         f = stdin;
9927         title = "stdin";
9928     } else {
9929         f = fopen(filename, "rb");
9930         if (f == NULL) {
9931           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9932             DisplayError(buf, errno);
9933             return FALSE;
9934         }
9935     }
9936     if (fseek(f, 0, 0) == -1) {
9937         /* f is not seekable; probably a pipe */
9938         useList = FALSE;
9939     }
9940     if (useList && n == 0) {
9941         int error = GameListBuild(f);
9942         if (error) {
9943             DisplayError(_("Cannot build game list"), error);
9944         } else if (!ListEmpty(&gameList) &&
9945                    ((ListGame *) gameList.tailPred)->number > 1) {
9946             GameListPopUp(f, title);
9947             return TRUE;
9948         }
9949         GameListDestroy();
9950         n = 1;
9951     }
9952     if (n == 0) n = 1;
9953     return LoadGame(f, n, title, FALSE);
9954 }
9955
9956
9957 void
9958 MakeRegisteredMove()
9959 {
9960     int fromX, fromY, toX, toY;
9961     char promoChar;
9962     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9963         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9964           case CMAIL_MOVE:
9965           case CMAIL_DRAW:
9966             if (appData.debugMode)
9967               fprintf(debugFP, "Restoring %s for game %d\n",
9968                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9969
9970             thinkOutput[0] = NULLCHAR;
9971             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9972             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9973             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9974             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9975             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9976             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9977             MakeMove(fromX, fromY, toX, toY, promoChar);
9978             ShowMove(fromX, fromY, toX, toY);
9979
9980             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9981               case MT_NONE:
9982               case MT_CHECK:
9983                 break;
9984
9985               case MT_CHECKMATE:
9986               case MT_STAINMATE:
9987                 if (WhiteOnMove(currentMove)) {
9988                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9989                 } else {
9990                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9991                 }
9992                 break;
9993
9994               case MT_STALEMATE:
9995                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9996                 break;
9997             }
9998
9999             break;
10000
10001           case CMAIL_RESIGN:
10002             if (WhiteOnMove(currentMove)) {
10003                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10004             } else {
10005                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10006             }
10007             break;
10008
10009           case CMAIL_ACCEPT:
10010             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10011             break;
10012
10013           default:
10014             break;
10015         }
10016     }
10017
10018     return;
10019 }
10020
10021 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10022 int
10023 CmailLoadGame(f, gameNumber, title, useList)
10024      FILE *f;
10025      int gameNumber;
10026      char *title;
10027      int useList;
10028 {
10029     int retVal;
10030
10031     if (gameNumber > nCmailGames) {
10032         DisplayError(_("No more games in this message"), 0);
10033         return FALSE;
10034     }
10035     if (f == lastLoadGameFP) {
10036         int offset = gameNumber - lastLoadGameNumber;
10037         if (offset == 0) {
10038             cmailMsg[0] = NULLCHAR;
10039             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10040                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10041                 nCmailMovesRegistered--;
10042             }
10043             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10044             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10045                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10046             }
10047         } else {
10048             if (! RegisterMove()) return FALSE;
10049         }
10050     }
10051
10052     retVal = LoadGame(f, gameNumber, title, useList);
10053
10054     /* Make move registered during previous look at this game, if any */
10055     MakeRegisteredMove();
10056
10057     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10058         commentList[currentMove]
10059           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10060         DisplayComment(currentMove - 1, commentList[currentMove]);
10061     }
10062
10063     return retVal;
10064 }
10065
10066 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10067 int
10068 ReloadGame(offset)
10069      int offset;
10070 {
10071     int gameNumber = lastLoadGameNumber + offset;
10072     if (lastLoadGameFP == NULL) {
10073         DisplayError(_("No game has been loaded yet"), 0);
10074         return FALSE;
10075     }
10076     if (gameNumber <= 0) {
10077         DisplayError(_("Can't back up any further"), 0);
10078         return FALSE;
10079     }
10080     if (cmailMsgLoaded) {
10081         return CmailLoadGame(lastLoadGameFP, gameNumber,
10082                              lastLoadGameTitle, lastLoadGameUseList);
10083     } else {
10084         return LoadGame(lastLoadGameFP, gameNumber,
10085                         lastLoadGameTitle, lastLoadGameUseList);
10086     }
10087 }
10088
10089
10090
10091 /* Load the nth game from open file f */
10092 int
10093 LoadGame(f, gameNumber, title, useList)
10094      FILE *f;
10095      int gameNumber;
10096      char *title;
10097      int useList;
10098 {
10099     ChessMove cm;
10100     char buf[MSG_SIZ];
10101     int gn = gameNumber;
10102     ListGame *lg = NULL;
10103     int numPGNTags = 0;
10104     int err;
10105     GameMode oldGameMode;
10106     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10107
10108     if (appData.debugMode)
10109         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10110
10111     if (gameMode == Training )
10112         SetTrainingModeOff();
10113
10114     oldGameMode = gameMode;
10115     if (gameMode != BeginningOfGame) {
10116       Reset(FALSE, TRUE);
10117     }
10118
10119     gameFileFP = f;
10120     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10121         fclose(lastLoadGameFP);
10122     }
10123
10124     if (useList) {
10125         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10126
10127         if (lg) {
10128             fseek(f, lg->offset, 0);
10129             GameListHighlight(gameNumber);
10130             gn = 1;
10131         }
10132         else {
10133             DisplayError(_("Game number out of range"), 0);
10134             return FALSE;
10135         }
10136     } else {
10137         GameListDestroy();
10138         if (fseek(f, 0, 0) == -1) {
10139             if (f == lastLoadGameFP ?
10140                 gameNumber == lastLoadGameNumber + 1 :
10141                 gameNumber == 1) {
10142                 gn = 1;
10143             } else {
10144                 DisplayError(_("Can't seek on game file"), 0);
10145                 return FALSE;
10146             }
10147         }
10148     }
10149     lastLoadGameFP = f;
10150     lastLoadGameNumber = gameNumber;
10151     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10152     lastLoadGameUseList = useList;
10153
10154     yynewfile(f);
10155
10156     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10157       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10158                 lg->gameInfo.black);
10159             DisplayTitle(buf);
10160     } else if (*title != NULLCHAR) {
10161         if (gameNumber > 1) {
10162           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10163             DisplayTitle(buf);
10164         } else {
10165             DisplayTitle(title);
10166         }
10167     }
10168
10169     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10170         gameMode = PlayFromGameFile;
10171         ModeHighlight();
10172     }
10173
10174     currentMove = forwardMostMove = backwardMostMove = 0;
10175     CopyBoard(boards[0], initialPosition);
10176     StopClocks();
10177
10178     /*
10179      * Skip the first gn-1 games in the file.
10180      * Also skip over anything that precedes an identifiable
10181      * start of game marker, to avoid being confused by
10182      * garbage at the start of the file.  Currently
10183      * recognized start of game markers are the move number "1",
10184      * the pattern "gnuchess .* game", the pattern
10185      * "^[#;%] [^ ]* game file", and a PGN tag block.
10186      * A game that starts with one of the latter two patterns
10187      * will also have a move number 1, possibly
10188      * following a position diagram.
10189      * 5-4-02: Let's try being more lenient and allowing a game to
10190      * start with an unnumbered move.  Does that break anything?
10191      */
10192     cm = lastLoadGameStart = EndOfFile;
10193     while (gn > 0) {
10194         yyboardindex = forwardMostMove;
10195         cm = (ChessMove) Myylex();
10196         switch (cm) {
10197           case EndOfFile:
10198             if (cmailMsgLoaded) {
10199                 nCmailGames = CMAIL_MAX_GAMES - gn;
10200             } else {
10201                 Reset(TRUE, TRUE);
10202                 DisplayError(_("Game not found in file"), 0);
10203             }
10204             return FALSE;
10205
10206           case GNUChessGame:
10207           case XBoardGame:
10208             gn--;
10209             lastLoadGameStart = cm;
10210             break;
10211
10212           case MoveNumberOne:
10213             switch (lastLoadGameStart) {
10214               case GNUChessGame:
10215               case XBoardGame:
10216               case PGNTag:
10217                 break;
10218               case MoveNumberOne:
10219               case EndOfFile:
10220                 gn--;           /* count this game */
10221                 lastLoadGameStart = cm;
10222                 break;
10223               default:
10224                 /* impossible */
10225                 break;
10226             }
10227             break;
10228
10229           case PGNTag:
10230             switch (lastLoadGameStart) {
10231               case GNUChessGame:
10232               case PGNTag:
10233               case MoveNumberOne:
10234               case EndOfFile:
10235                 gn--;           /* count this game */
10236                 lastLoadGameStart = cm;
10237                 break;
10238               case XBoardGame:
10239                 lastLoadGameStart = cm; /* game counted already */
10240                 break;
10241               default:
10242                 /* impossible */
10243                 break;
10244             }
10245             if (gn > 0) {
10246                 do {
10247                     yyboardindex = forwardMostMove;
10248                     cm = (ChessMove) Myylex();
10249                 } while (cm == PGNTag || cm == Comment);
10250             }
10251             break;
10252
10253           case WhiteWins:
10254           case BlackWins:
10255           case GameIsDrawn:
10256             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10257                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10258                     != CMAIL_OLD_RESULT) {
10259                     nCmailResults ++ ;
10260                     cmailResult[  CMAIL_MAX_GAMES
10261                                 - gn - 1] = CMAIL_OLD_RESULT;
10262                 }
10263             }
10264             break;
10265
10266           case NormalMove:
10267             /* Only a NormalMove can be at the start of a game
10268              * without a position diagram. */
10269             if (lastLoadGameStart == EndOfFile ) {
10270               gn--;
10271               lastLoadGameStart = MoveNumberOne;
10272             }
10273             break;
10274
10275           default:
10276             break;
10277         }
10278     }
10279
10280     if (appData.debugMode)
10281       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10282
10283     if (cm == XBoardGame) {
10284         /* Skip any header junk before position diagram and/or move 1 */
10285         for (;;) {
10286             yyboardindex = forwardMostMove;
10287             cm = (ChessMove) Myylex();
10288
10289             if (cm == EndOfFile ||
10290                 cm == GNUChessGame || cm == XBoardGame) {
10291                 /* Empty game; pretend end-of-file and handle later */
10292                 cm = EndOfFile;
10293                 break;
10294             }
10295
10296             if (cm == MoveNumberOne || cm == PositionDiagram ||
10297                 cm == PGNTag || cm == Comment)
10298               break;
10299         }
10300     } else if (cm == GNUChessGame) {
10301         if (gameInfo.event != NULL) {
10302             free(gameInfo.event);
10303         }
10304         gameInfo.event = StrSave(yy_text);
10305     }
10306
10307     startedFromSetupPosition = FALSE;
10308     while (cm == PGNTag) {
10309         if (appData.debugMode)
10310           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10311         err = ParsePGNTag(yy_text, &gameInfo);
10312         if (!err) numPGNTags++;
10313
10314         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10315         if(gameInfo.variant != oldVariant) {
10316             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10317             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10318             InitPosition(TRUE);
10319             oldVariant = gameInfo.variant;
10320             if (appData.debugMode)
10321               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10322         }
10323
10324
10325         if (gameInfo.fen != NULL) {
10326           Board initial_position;
10327           startedFromSetupPosition = TRUE;
10328           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10329             Reset(TRUE, TRUE);
10330             DisplayError(_("Bad FEN position in file"), 0);
10331             return FALSE;
10332           }
10333           CopyBoard(boards[0], initial_position);
10334           if (blackPlaysFirst) {
10335             currentMove = forwardMostMove = backwardMostMove = 1;
10336             CopyBoard(boards[1], initial_position);
10337             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10338             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10339             timeRemaining[0][1] = whiteTimeRemaining;
10340             timeRemaining[1][1] = blackTimeRemaining;
10341             if (commentList[0] != NULL) {
10342               commentList[1] = commentList[0];
10343               commentList[0] = NULL;
10344             }
10345           } else {
10346             currentMove = forwardMostMove = backwardMostMove = 0;
10347           }
10348           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10349           {   int i;
10350               initialRulePlies = FENrulePlies;
10351               for( i=0; i< nrCastlingRights; i++ )
10352                   initialRights[i] = initial_position[CASTLING][i];
10353           }
10354           yyboardindex = forwardMostMove;
10355           free(gameInfo.fen);
10356           gameInfo.fen = NULL;
10357         }
10358
10359         yyboardindex = forwardMostMove;
10360         cm = (ChessMove) Myylex();
10361
10362         /* Handle comments interspersed among the tags */
10363         while (cm == Comment) {
10364             char *p;
10365             if (appData.debugMode)
10366               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10367             p = yy_text;
10368             AppendComment(currentMove, p, FALSE);
10369             yyboardindex = forwardMostMove;
10370             cm = (ChessMove) Myylex();
10371         }
10372     }
10373
10374     /* don't rely on existence of Event tag since if game was
10375      * pasted from clipboard the Event tag may not exist
10376      */
10377     if (numPGNTags > 0){
10378         char *tags;
10379         if (gameInfo.variant == VariantNormal) {
10380           VariantClass v = StringToVariant(gameInfo.event);
10381           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10382           if(v < VariantShogi) gameInfo.variant = v;
10383         }
10384         if (!matchMode) {
10385           if( appData.autoDisplayTags ) {
10386             tags = PGNTags(&gameInfo);
10387             TagsPopUp(tags, CmailMsg());
10388             free(tags);
10389           }
10390         }
10391     } else {
10392         /* Make something up, but don't display it now */
10393         SetGameInfo();
10394         TagsPopDown();
10395     }
10396
10397     if (cm == PositionDiagram) {
10398         int i, j;
10399         char *p;
10400         Board initial_position;
10401
10402         if (appData.debugMode)
10403           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10404
10405         if (!startedFromSetupPosition) {
10406             p = yy_text;
10407             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10408               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10409                 switch (*p) {
10410                   case '[':
10411                   case '-':
10412                   case ' ':
10413                   case '\t':
10414                   case '\n':
10415                   case '\r':
10416                     break;
10417                   default:
10418                     initial_position[i][j++] = CharToPiece(*p);
10419                     break;
10420                 }
10421             while (*p == ' ' || *p == '\t' ||
10422                    *p == '\n' || *p == '\r') p++;
10423
10424             if (strncmp(p, "black", strlen("black"))==0)
10425               blackPlaysFirst = TRUE;
10426             else
10427               blackPlaysFirst = FALSE;
10428             startedFromSetupPosition = TRUE;
10429
10430             CopyBoard(boards[0], initial_position);
10431             if (blackPlaysFirst) {
10432                 currentMove = forwardMostMove = backwardMostMove = 1;
10433                 CopyBoard(boards[1], initial_position);
10434                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10435                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10436                 timeRemaining[0][1] = whiteTimeRemaining;
10437                 timeRemaining[1][1] = blackTimeRemaining;
10438                 if (commentList[0] != NULL) {
10439                     commentList[1] = commentList[0];
10440                     commentList[0] = NULL;
10441                 }
10442             } else {
10443                 currentMove = forwardMostMove = backwardMostMove = 0;
10444             }
10445         }
10446         yyboardindex = forwardMostMove;
10447         cm = (ChessMove) Myylex();
10448     }
10449
10450     if (first.pr == NoProc) {
10451         StartChessProgram(&first);
10452     }
10453     InitChessProgram(&first, FALSE);
10454     SendToProgram("force\n", &first);
10455     if (startedFromSetupPosition) {
10456         SendBoard(&first, forwardMostMove);
10457     if (appData.debugMode) {
10458         fprintf(debugFP, "Load Game\n");
10459     }
10460         DisplayBothClocks();
10461     }
10462
10463     /* [HGM] server: flag to write setup moves in broadcast file as one */
10464     loadFlag = appData.suppressLoadMoves;
10465
10466     while (cm == Comment) {
10467         char *p;
10468         if (appData.debugMode)
10469           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10470         p = yy_text;
10471         AppendComment(currentMove, p, FALSE);
10472         yyboardindex = forwardMostMove;
10473         cm = (ChessMove) Myylex();
10474     }
10475
10476     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10477         cm == WhiteWins || cm == BlackWins ||
10478         cm == GameIsDrawn || cm == GameUnfinished) {
10479         DisplayMessage("", _("No moves in game"));
10480         if (cmailMsgLoaded) {
10481             if (appData.debugMode)
10482               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10483             ClearHighlights();
10484             flipView = FALSE;
10485         }
10486         DrawPosition(FALSE, boards[currentMove]);
10487         DisplayBothClocks();
10488         gameMode = EditGame;
10489         ModeHighlight();
10490         gameFileFP = NULL;
10491         cmailOldMove = 0;
10492         return TRUE;
10493     }
10494
10495     // [HGM] PV info: routine tests if comment empty
10496     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10497         DisplayComment(currentMove - 1, commentList[currentMove]);
10498     }
10499     if (!matchMode && appData.timeDelay != 0)
10500       DrawPosition(FALSE, boards[currentMove]);
10501
10502     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10503       programStats.ok_to_send = 1;
10504     }
10505
10506     /* if the first token after the PGN tags is a move
10507      * and not move number 1, retrieve it from the parser
10508      */
10509     if (cm != MoveNumberOne)
10510         LoadGameOneMove(cm);
10511
10512     /* load the remaining moves from the file */
10513     while (LoadGameOneMove(EndOfFile)) {
10514       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10515       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10516     }
10517
10518     /* rewind to the start of the game */
10519     currentMove = backwardMostMove;
10520
10521     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10522
10523     if (oldGameMode == AnalyzeFile ||
10524         oldGameMode == AnalyzeMode) {
10525       AnalyzeFileEvent();
10526     }
10527
10528     if (matchMode || appData.timeDelay == 0) {
10529       ToEndEvent();
10530       gameMode = EditGame;
10531       ModeHighlight();
10532     } else if (appData.timeDelay > 0) {
10533       AutoPlayGameLoop();
10534     }
10535
10536     if (appData.debugMode)
10537         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10538
10539     loadFlag = 0; /* [HGM] true game starts */
10540     return TRUE;
10541 }
10542
10543 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10544 int
10545 ReloadPosition(offset)
10546      int offset;
10547 {
10548     int positionNumber = lastLoadPositionNumber + offset;
10549     if (lastLoadPositionFP == NULL) {
10550         DisplayError(_("No position has been loaded yet"), 0);
10551         return FALSE;
10552     }
10553     if (positionNumber <= 0) {
10554         DisplayError(_("Can't back up any further"), 0);
10555         return FALSE;
10556     }
10557     return LoadPosition(lastLoadPositionFP, positionNumber,
10558                         lastLoadPositionTitle);
10559 }
10560
10561 /* Load the nth position from the given file */
10562 int
10563 LoadPositionFromFile(filename, n, title)
10564      char *filename;
10565      int n;
10566      char *title;
10567 {
10568     FILE *f;
10569     char buf[MSG_SIZ];
10570
10571     if (strcmp(filename, "-") == 0) {
10572         return LoadPosition(stdin, n, "stdin");
10573     } else {
10574         f = fopen(filename, "rb");
10575         if (f == NULL) {
10576             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10577             DisplayError(buf, errno);
10578             return FALSE;
10579         } else {
10580             return LoadPosition(f, n, title);
10581         }
10582     }
10583 }
10584
10585 /* Load the nth position from the given open file, and close it */
10586 int
10587 LoadPosition(f, positionNumber, title)
10588      FILE *f;
10589      int positionNumber;
10590      char *title;
10591 {
10592     char *p, line[MSG_SIZ];
10593     Board initial_position;
10594     int i, j, fenMode, pn;
10595
10596     if (gameMode == Training )
10597         SetTrainingModeOff();
10598
10599     if (gameMode != BeginningOfGame) {
10600         Reset(FALSE, TRUE);
10601     }
10602     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10603         fclose(lastLoadPositionFP);
10604     }
10605     if (positionNumber == 0) positionNumber = 1;
10606     lastLoadPositionFP = f;
10607     lastLoadPositionNumber = positionNumber;
10608     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10609     if (first.pr == NoProc) {
10610       StartChessProgram(&first);
10611       InitChessProgram(&first, FALSE);
10612     }
10613     pn = positionNumber;
10614     if (positionNumber < 0) {
10615         /* Negative position number means to seek to that byte offset */
10616         if (fseek(f, -positionNumber, 0) == -1) {
10617             DisplayError(_("Can't seek on position file"), 0);
10618             return FALSE;
10619         };
10620         pn = 1;
10621     } else {
10622         if (fseek(f, 0, 0) == -1) {
10623             if (f == lastLoadPositionFP ?
10624                 positionNumber == lastLoadPositionNumber + 1 :
10625                 positionNumber == 1) {
10626                 pn = 1;
10627             } else {
10628                 DisplayError(_("Can't seek on position file"), 0);
10629                 return FALSE;
10630             }
10631         }
10632     }
10633     /* See if this file is FEN or old-style xboard */
10634     if (fgets(line, MSG_SIZ, f) == NULL) {
10635         DisplayError(_("Position not found in file"), 0);
10636         return FALSE;
10637     }
10638     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10639     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10640
10641     if (pn >= 2) {
10642         if (fenMode || line[0] == '#') pn--;
10643         while (pn > 0) {
10644             /* skip positions before number pn */
10645             if (fgets(line, MSG_SIZ, f) == NULL) {
10646                 Reset(TRUE, TRUE);
10647                 DisplayError(_("Position not found in file"), 0);
10648                 return FALSE;
10649             }
10650             if (fenMode || line[0] == '#') pn--;
10651         }
10652     }
10653
10654     if (fenMode) {
10655         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10656             DisplayError(_("Bad FEN position in file"), 0);
10657             return FALSE;
10658         }
10659     } else {
10660         (void) fgets(line, MSG_SIZ, f);
10661         (void) fgets(line, MSG_SIZ, f);
10662
10663         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10664             (void) fgets(line, MSG_SIZ, f);
10665             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10666                 if (*p == ' ')
10667                   continue;
10668                 initial_position[i][j++] = CharToPiece(*p);
10669             }
10670         }
10671
10672         blackPlaysFirst = FALSE;
10673         if (!feof(f)) {
10674             (void) fgets(line, MSG_SIZ, f);
10675             if (strncmp(line, "black", strlen("black"))==0)
10676               blackPlaysFirst = TRUE;
10677         }
10678     }
10679     startedFromSetupPosition = TRUE;
10680
10681     SendToProgram("force\n", &first);
10682     CopyBoard(boards[0], initial_position);
10683     if (blackPlaysFirst) {
10684         currentMove = forwardMostMove = backwardMostMove = 1;
10685         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10686         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10687         CopyBoard(boards[1], initial_position);
10688         DisplayMessage("", _("Black to play"));
10689     } else {
10690         currentMove = forwardMostMove = backwardMostMove = 0;
10691         DisplayMessage("", _("White to play"));
10692     }
10693     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10694     SendBoard(&first, forwardMostMove);
10695     if (appData.debugMode) {
10696 int i, j;
10697   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10698   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10699         fprintf(debugFP, "Load Position\n");
10700     }
10701
10702     if (positionNumber > 1) {
10703       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10704         DisplayTitle(line);
10705     } else {
10706         DisplayTitle(title);
10707     }
10708     gameMode = EditGame;
10709     ModeHighlight();
10710     ResetClocks();
10711     timeRemaining[0][1] = whiteTimeRemaining;
10712     timeRemaining[1][1] = blackTimeRemaining;
10713     DrawPosition(FALSE, boards[currentMove]);
10714
10715     return TRUE;
10716 }
10717
10718
10719 void
10720 CopyPlayerNameIntoFileName(dest, src)
10721      char **dest, *src;
10722 {
10723     while (*src != NULLCHAR && *src != ',') {
10724         if (*src == ' ') {
10725             *(*dest)++ = '_';
10726             src++;
10727         } else {
10728             *(*dest)++ = *src++;
10729         }
10730     }
10731 }
10732
10733 char *DefaultFileName(ext)
10734      char *ext;
10735 {
10736     static char def[MSG_SIZ];
10737     char *p;
10738
10739     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10740         p = def;
10741         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10742         *p++ = '-';
10743         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10744         *p++ = '.';
10745         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10746     } else {
10747         def[0] = NULLCHAR;
10748     }
10749     return def;
10750 }
10751
10752 /* Save the current game to the given file */
10753 int
10754 SaveGameToFile(filename, append)
10755      char *filename;
10756      int append;
10757 {
10758     FILE *f;
10759     char buf[MSG_SIZ];
10760
10761     if (strcmp(filename, "-") == 0) {
10762         return SaveGame(stdout, 0, NULL);
10763     } else {
10764         f = fopen(filename, append ? "a" : "w");
10765         if (f == NULL) {
10766             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10767             DisplayError(buf, errno);
10768             return FALSE;
10769         } else {
10770             return SaveGame(f, 0, NULL);
10771         }
10772     }
10773 }
10774
10775 char *
10776 SavePart(str)
10777      char *str;
10778 {
10779     static char buf[MSG_SIZ];
10780     char *p;
10781
10782     p = strchr(str, ' ');
10783     if (p == NULL) return str;
10784     strncpy(buf, str, p - str);
10785     buf[p - str] = NULLCHAR;
10786     return buf;
10787 }
10788
10789 #define PGN_MAX_LINE 75
10790
10791 #define PGN_SIDE_WHITE  0
10792 #define PGN_SIDE_BLACK  1
10793
10794 /* [AS] */
10795 static int FindFirstMoveOutOfBook( int side )
10796 {
10797     int result = -1;
10798
10799     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10800         int index = backwardMostMove;
10801         int has_book_hit = 0;
10802
10803         if( (index % 2) != side ) {
10804             index++;
10805         }
10806
10807         while( index < forwardMostMove ) {
10808             /* Check to see if engine is in book */
10809             int depth = pvInfoList[index].depth;
10810             int score = pvInfoList[index].score;
10811             int in_book = 0;
10812
10813             if( depth <= 2 ) {
10814                 in_book = 1;
10815             }
10816             else if( score == 0 && depth == 63 ) {
10817                 in_book = 1; /* Zappa */
10818             }
10819             else if( score == 2 && depth == 99 ) {
10820                 in_book = 1; /* Abrok */
10821             }
10822
10823             has_book_hit += in_book;
10824
10825             if( ! in_book ) {
10826                 result = index;
10827
10828                 break;
10829             }
10830
10831             index += 2;
10832         }
10833     }
10834
10835     return result;
10836 }
10837
10838 /* [AS] */
10839 void GetOutOfBookInfo( char * buf )
10840 {
10841     int oob[2];
10842     int i;
10843     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10844
10845     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10846     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10847
10848     *buf = '\0';
10849
10850     if( oob[0] >= 0 || oob[1] >= 0 ) {
10851         for( i=0; i<2; i++ ) {
10852             int idx = oob[i];
10853
10854             if( idx >= 0 ) {
10855                 if( i > 0 && oob[0] >= 0 ) {
10856                     strcat( buf, "   " );
10857                 }
10858
10859                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10860                 sprintf( buf+strlen(buf), "%s%.2f",
10861                     pvInfoList[idx].score >= 0 ? "+" : "",
10862                     pvInfoList[idx].score / 100.0 );
10863             }
10864         }
10865     }
10866 }
10867
10868 /* Save game in PGN style and close the file */
10869 int
10870 SaveGamePGN(f)
10871      FILE *f;
10872 {
10873     int i, offset, linelen, newblock;
10874     time_t tm;
10875 //    char *movetext;
10876     char numtext[32];
10877     int movelen, numlen, blank;
10878     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10879
10880     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10881
10882     tm = time((time_t *) NULL);
10883
10884     PrintPGNTags(f, &gameInfo);
10885
10886     if (backwardMostMove > 0 || startedFromSetupPosition) {
10887         char *fen = PositionToFEN(backwardMostMove, NULL);
10888         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10889         fprintf(f, "\n{--------------\n");
10890         PrintPosition(f, backwardMostMove);
10891         fprintf(f, "--------------}\n");
10892         free(fen);
10893     }
10894     else {
10895         /* [AS] Out of book annotation */
10896         if( appData.saveOutOfBookInfo ) {
10897             char buf[64];
10898
10899             GetOutOfBookInfo( buf );
10900
10901             if( buf[0] != '\0' ) {
10902                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10903             }
10904         }
10905
10906         fprintf(f, "\n");
10907     }
10908
10909     i = backwardMostMove;
10910     linelen = 0;
10911     newblock = TRUE;
10912
10913     while (i < forwardMostMove) {
10914         /* Print comments preceding this move */
10915         if (commentList[i] != NULL) {
10916             if (linelen > 0) fprintf(f, "\n");
10917             fprintf(f, "%s", commentList[i]);
10918             linelen = 0;
10919             newblock = TRUE;
10920         }
10921
10922         /* Format move number */
10923         if ((i % 2) == 0)
10924           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10925         else
10926           if (newblock)
10927             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10928           else
10929             numtext[0] = NULLCHAR;
10930
10931         numlen = strlen(numtext);
10932         newblock = FALSE;
10933
10934         /* Print move number */
10935         blank = linelen > 0 && numlen > 0;
10936         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10937             fprintf(f, "\n");
10938             linelen = 0;
10939             blank = 0;
10940         }
10941         if (blank) {
10942             fprintf(f, " ");
10943             linelen++;
10944         }
10945         fprintf(f, "%s", numtext);
10946         linelen += numlen;
10947
10948         /* Get move */
10949         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10950         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10951
10952         /* Print move */
10953         blank = linelen > 0 && movelen > 0;
10954         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10955             fprintf(f, "\n");
10956             linelen = 0;
10957             blank = 0;
10958         }
10959         if (blank) {
10960             fprintf(f, " ");
10961             linelen++;
10962         }
10963         fprintf(f, "%s", move_buffer);
10964         linelen += movelen;
10965
10966         /* [AS] Add PV info if present */
10967         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10968             /* [HGM] add time */
10969             char buf[MSG_SIZ]; int seconds;
10970
10971             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10972
10973             if( seconds <= 0)
10974               buf[0] = 0;
10975             else
10976               if( seconds < 30 )
10977                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10978               else
10979                 {
10980                   seconds = (seconds + 4)/10; // round to full seconds
10981                   if( seconds < 60 )
10982                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10983                   else
10984                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10985                 }
10986
10987             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10988                       pvInfoList[i].score >= 0 ? "+" : "",
10989                       pvInfoList[i].score / 100.0,
10990                       pvInfoList[i].depth,
10991                       buf );
10992
10993             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10994
10995             /* Print score/depth */
10996             blank = linelen > 0 && movelen > 0;
10997             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10998                 fprintf(f, "\n");
10999                 linelen = 0;
11000                 blank = 0;
11001             }
11002             if (blank) {
11003                 fprintf(f, " ");
11004                 linelen++;
11005             }
11006             fprintf(f, "%s", move_buffer);
11007             linelen += movelen;
11008         }
11009
11010         i++;
11011     }
11012
11013     /* Start a new line */
11014     if (linelen > 0) fprintf(f, "\n");
11015
11016     /* Print comments after last move */
11017     if (commentList[i] != NULL) {
11018         fprintf(f, "%s\n", commentList[i]);
11019     }
11020
11021     /* Print result */
11022     if (gameInfo.resultDetails != NULL &&
11023         gameInfo.resultDetails[0] != NULLCHAR) {
11024         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11025                 PGNResult(gameInfo.result));
11026     } else {
11027         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11028     }
11029
11030     fclose(f);
11031     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11032     return TRUE;
11033 }
11034
11035 /* Save game in old style and close the file */
11036 int
11037 SaveGameOldStyle(f)
11038      FILE *f;
11039 {
11040     int i, offset;
11041     time_t tm;
11042
11043     tm = time((time_t *) NULL);
11044
11045     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11046     PrintOpponents(f);
11047
11048     if (backwardMostMove > 0 || startedFromSetupPosition) {
11049         fprintf(f, "\n[--------------\n");
11050         PrintPosition(f, backwardMostMove);
11051         fprintf(f, "--------------]\n");
11052     } else {
11053         fprintf(f, "\n");
11054     }
11055
11056     i = backwardMostMove;
11057     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11058
11059     while (i < forwardMostMove) {
11060         if (commentList[i] != NULL) {
11061             fprintf(f, "[%s]\n", commentList[i]);
11062         }
11063
11064         if ((i % 2) == 1) {
11065             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11066             i++;
11067         } else {
11068             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11069             i++;
11070             if (commentList[i] != NULL) {
11071                 fprintf(f, "\n");
11072                 continue;
11073             }
11074             if (i >= forwardMostMove) {
11075                 fprintf(f, "\n");
11076                 break;
11077             }
11078             fprintf(f, "%s\n", parseList[i]);
11079             i++;
11080         }
11081     }
11082
11083     if (commentList[i] != NULL) {
11084         fprintf(f, "[%s]\n", commentList[i]);
11085     }
11086
11087     /* This isn't really the old style, but it's close enough */
11088     if (gameInfo.resultDetails != NULL &&
11089         gameInfo.resultDetails[0] != NULLCHAR) {
11090         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11091                 gameInfo.resultDetails);
11092     } else {
11093         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11094     }
11095
11096     fclose(f);
11097     return TRUE;
11098 }
11099
11100 /* Save the current game to open file f and close the file */
11101 int
11102 SaveGame(f, dummy, dummy2)
11103      FILE *f;
11104      int dummy;
11105      char *dummy2;
11106 {
11107     if (gameMode == EditPosition) EditPositionDone(TRUE);
11108     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11109     if (appData.oldSaveStyle)
11110       return SaveGameOldStyle(f);
11111     else
11112       return SaveGamePGN(f);
11113 }
11114
11115 /* Save the current position to the given file */
11116 int
11117 SavePositionToFile(filename)
11118      char *filename;
11119 {
11120     FILE *f;
11121     char buf[MSG_SIZ];
11122
11123     if (strcmp(filename, "-") == 0) {
11124         return SavePosition(stdout, 0, NULL);
11125     } else {
11126         f = fopen(filename, "a");
11127         if (f == NULL) {
11128             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11129             DisplayError(buf, errno);
11130             return FALSE;
11131         } else {
11132             SavePosition(f, 0, NULL);
11133             return TRUE;
11134         }
11135     }
11136 }
11137
11138 /* Save the current position to the given open file and close the file */
11139 int
11140 SavePosition(f, dummy, dummy2)
11141      FILE *f;
11142      int dummy;
11143      char *dummy2;
11144 {
11145     time_t tm;
11146     char *fen;
11147
11148     if (gameMode == EditPosition) EditPositionDone(TRUE);
11149     if (appData.oldSaveStyle) {
11150         tm = time((time_t *) NULL);
11151
11152         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11153         PrintOpponents(f);
11154         fprintf(f, "[--------------\n");
11155         PrintPosition(f, currentMove);
11156         fprintf(f, "--------------]\n");
11157     } else {
11158         fen = PositionToFEN(currentMove, NULL);
11159         fprintf(f, "%s\n", fen);
11160         free(fen);
11161     }
11162     fclose(f);
11163     return TRUE;
11164 }
11165
11166 void
11167 ReloadCmailMsgEvent(unregister)
11168      int unregister;
11169 {
11170 #if !WIN32
11171     static char *inFilename = NULL;
11172     static char *outFilename;
11173     int i;
11174     struct stat inbuf, outbuf;
11175     int status;
11176
11177     /* Any registered moves are unregistered if unregister is set, */
11178     /* i.e. invoked by the signal handler */
11179     if (unregister) {
11180         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11181             cmailMoveRegistered[i] = FALSE;
11182             if (cmailCommentList[i] != NULL) {
11183                 free(cmailCommentList[i]);
11184                 cmailCommentList[i] = NULL;
11185             }
11186         }
11187         nCmailMovesRegistered = 0;
11188     }
11189
11190     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11191         cmailResult[i] = CMAIL_NOT_RESULT;
11192     }
11193     nCmailResults = 0;
11194
11195     if (inFilename == NULL) {
11196         /* Because the filenames are static they only get malloced once  */
11197         /* and they never get freed                                      */
11198         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11199         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11200
11201         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11202         sprintf(outFilename, "%s.out", appData.cmailGameName);
11203     }
11204
11205     status = stat(outFilename, &outbuf);
11206     if (status < 0) {
11207         cmailMailedMove = FALSE;
11208     } else {
11209         status = stat(inFilename, &inbuf);
11210         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11211     }
11212
11213     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11214        counts the games, notes how each one terminated, etc.
11215
11216        It would be nice to remove this kludge and instead gather all
11217        the information while building the game list.  (And to keep it
11218        in the game list nodes instead of having a bunch of fixed-size
11219        parallel arrays.)  Note this will require getting each game's
11220        termination from the PGN tags, as the game list builder does
11221        not process the game moves.  --mann
11222        */
11223     cmailMsgLoaded = TRUE;
11224     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11225
11226     /* Load first game in the file or popup game menu */
11227     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11228
11229 #endif /* !WIN32 */
11230     return;
11231 }
11232
11233 int
11234 RegisterMove()
11235 {
11236     FILE *f;
11237     char string[MSG_SIZ];
11238
11239     if (   cmailMailedMove
11240         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11241         return TRUE;            /* Allow free viewing  */
11242     }
11243
11244     /* Unregister move to ensure that we don't leave RegisterMove        */
11245     /* with the move registered when the conditions for registering no   */
11246     /* longer hold                                                       */
11247     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11248         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11249         nCmailMovesRegistered --;
11250
11251         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11252           {
11253               free(cmailCommentList[lastLoadGameNumber - 1]);
11254               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11255           }
11256     }
11257
11258     if (cmailOldMove == -1) {
11259         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11260         return FALSE;
11261     }
11262
11263     if (currentMove > cmailOldMove + 1) {
11264         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11265         return FALSE;
11266     }
11267
11268     if (currentMove < cmailOldMove) {
11269         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11270         return FALSE;
11271     }
11272
11273     if (forwardMostMove > currentMove) {
11274         /* Silently truncate extra moves */
11275         TruncateGame();
11276     }
11277
11278     if (   (currentMove == cmailOldMove + 1)
11279         || (   (currentMove == cmailOldMove)
11280             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11281                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11282         if (gameInfo.result != GameUnfinished) {
11283             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11284         }
11285
11286         if (commentList[currentMove] != NULL) {
11287             cmailCommentList[lastLoadGameNumber - 1]
11288               = StrSave(commentList[currentMove]);
11289         }
11290         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11291
11292         if (appData.debugMode)
11293           fprintf(debugFP, "Saving %s for game %d\n",
11294                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11295
11296         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11297
11298         f = fopen(string, "w");
11299         if (appData.oldSaveStyle) {
11300             SaveGameOldStyle(f); /* also closes the file */
11301
11302             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11303             f = fopen(string, "w");
11304             SavePosition(f, 0, NULL); /* also closes the file */
11305         } else {
11306             fprintf(f, "{--------------\n");
11307             PrintPosition(f, currentMove);
11308             fprintf(f, "--------------}\n\n");
11309
11310             SaveGame(f, 0, NULL); /* also closes the file*/
11311         }
11312
11313         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11314         nCmailMovesRegistered ++;
11315     } else if (nCmailGames == 1) {
11316         DisplayError(_("You have not made a move yet"), 0);
11317         return FALSE;
11318     }
11319
11320     return TRUE;
11321 }
11322
11323 void
11324 MailMoveEvent()
11325 {
11326 #if !WIN32
11327     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11328     FILE *commandOutput;
11329     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11330     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11331     int nBuffers;
11332     int i;
11333     int archived;
11334     char *arcDir;
11335
11336     if (! cmailMsgLoaded) {
11337         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11338         return;
11339     }
11340
11341     if (nCmailGames == nCmailResults) {
11342         DisplayError(_("No unfinished games"), 0);
11343         return;
11344     }
11345
11346 #if CMAIL_PROHIBIT_REMAIL
11347     if (cmailMailedMove) {
11348       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);
11349         DisplayError(msg, 0);
11350         return;
11351     }
11352 #endif
11353
11354     if (! (cmailMailedMove || RegisterMove())) return;
11355
11356     if (   cmailMailedMove
11357         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11358       snprintf(string, MSG_SIZ, partCommandString,
11359                appData.debugMode ? " -v" : "", appData.cmailGameName);
11360         commandOutput = popen(string, "r");
11361
11362         if (commandOutput == NULL) {
11363             DisplayError(_("Failed to invoke cmail"), 0);
11364         } else {
11365             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11366                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11367             }
11368             if (nBuffers > 1) {
11369                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11370                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11371                 nBytes = MSG_SIZ - 1;
11372             } else {
11373                 (void) memcpy(msg, buffer, nBytes);
11374             }
11375             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11376
11377             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11378                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11379
11380                 archived = TRUE;
11381                 for (i = 0; i < nCmailGames; i ++) {
11382                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11383                         archived = FALSE;
11384                     }
11385                 }
11386                 if (   archived
11387                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11388                         != NULL)) {
11389                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11390                            arcDir,
11391                            appData.cmailGameName,
11392                            gameInfo.date);
11393                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11394                     cmailMsgLoaded = FALSE;
11395                 }
11396             }
11397
11398             DisplayInformation(msg);
11399             pclose(commandOutput);
11400         }
11401     } else {
11402         if ((*cmailMsg) != '\0') {
11403             DisplayInformation(cmailMsg);
11404         }
11405     }
11406
11407     return;
11408 #endif /* !WIN32 */
11409 }
11410
11411 char *
11412 CmailMsg()
11413 {
11414 #if WIN32
11415     return NULL;
11416 #else
11417     int  prependComma = 0;
11418     char number[5];
11419     char string[MSG_SIZ];       /* Space for game-list */
11420     int  i;
11421
11422     if (!cmailMsgLoaded) return "";
11423
11424     if (cmailMailedMove) {
11425       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11426     } else {
11427         /* Create a list of games left */
11428       snprintf(string, MSG_SIZ, "[");
11429         for (i = 0; i < nCmailGames; i ++) {
11430             if (! (   cmailMoveRegistered[i]
11431                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11432                 if (prependComma) {
11433                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11434                 } else {
11435                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11436                     prependComma = 1;
11437                 }
11438
11439                 strcat(string, number);
11440             }
11441         }
11442         strcat(string, "]");
11443
11444         if (nCmailMovesRegistered + nCmailResults == 0) {
11445             switch (nCmailGames) {
11446               case 1:
11447                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11448                 break;
11449
11450               case 2:
11451                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11452                 break;
11453
11454               default:
11455                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11456                          nCmailGames);
11457                 break;
11458             }
11459         } else {
11460             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11461               case 1:
11462                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11463                          string);
11464                 break;
11465
11466               case 0:
11467                 if (nCmailResults == nCmailGames) {
11468                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11469                 } else {
11470                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11471                 }
11472                 break;
11473
11474               default:
11475                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11476                          string);
11477             }
11478         }
11479     }
11480     return cmailMsg;
11481 #endif /* WIN32 */
11482 }
11483
11484 void
11485 ResetGameEvent()
11486 {
11487     if (gameMode == Training)
11488       SetTrainingModeOff();
11489
11490     Reset(TRUE, TRUE);
11491     cmailMsgLoaded = FALSE;
11492     if (appData.icsActive) {
11493       SendToICS(ics_prefix);
11494       SendToICS("refresh\n");
11495     }
11496 }
11497
11498 void
11499 ExitEvent(status)
11500      int status;
11501 {
11502     exiting++;
11503     if (exiting > 2) {
11504       /* Give up on clean exit */
11505       exit(status);
11506     }
11507     if (exiting > 1) {
11508       /* Keep trying for clean exit */
11509       return;
11510     }
11511
11512     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11513
11514     if (telnetISR != NULL) {
11515       RemoveInputSource(telnetISR);
11516     }
11517     if (icsPR != NoProc) {
11518       DestroyChildProcess(icsPR, TRUE);
11519     }
11520
11521     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11522     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11523
11524     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11525     /* make sure this other one finishes before killing it!                  */
11526     if(endingGame) { int count = 0;
11527         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11528         while(endingGame && count++ < 10) DoSleep(1);
11529         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11530     }
11531
11532     /* Kill off chess programs */
11533     if (first.pr != NoProc) {
11534         ExitAnalyzeMode();
11535
11536         DoSleep( appData.delayBeforeQuit );
11537         SendToProgram("quit\n", &first);
11538         DoSleep( appData.delayAfterQuit );
11539         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11540     }
11541     if (second.pr != NoProc) {
11542         DoSleep( appData.delayBeforeQuit );
11543         SendToProgram("quit\n", &second);
11544         DoSleep( appData.delayAfterQuit );
11545         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11546     }
11547     if (first.isr != NULL) {
11548         RemoveInputSource(first.isr);
11549     }
11550     if (second.isr != NULL) {
11551         RemoveInputSource(second.isr);
11552     }
11553
11554     ShutDownFrontEnd();
11555     exit(status);
11556 }
11557
11558 void
11559 PauseEvent()
11560 {
11561     if (appData.debugMode)
11562         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11563     if (pausing) {
11564         pausing = FALSE;
11565         ModeHighlight();
11566         if (gameMode == MachinePlaysWhite ||
11567             gameMode == MachinePlaysBlack) {
11568             StartClocks();
11569         } else {
11570             DisplayBothClocks();
11571         }
11572         if (gameMode == PlayFromGameFile) {
11573             if (appData.timeDelay >= 0)
11574                 AutoPlayGameLoop();
11575         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11576             Reset(FALSE, TRUE);
11577             SendToICS(ics_prefix);
11578             SendToICS("refresh\n");
11579         } else if (currentMove < forwardMostMove) {
11580             ForwardInner(forwardMostMove);
11581         }
11582         pauseExamInvalid = FALSE;
11583     } else {
11584         switch (gameMode) {
11585           default:
11586             return;
11587           case IcsExamining:
11588             pauseExamForwardMostMove = forwardMostMove;
11589             pauseExamInvalid = FALSE;
11590             /* fall through */
11591           case IcsObserving:
11592           case IcsPlayingWhite:
11593           case IcsPlayingBlack:
11594             pausing = TRUE;
11595             ModeHighlight();
11596             return;
11597           case PlayFromGameFile:
11598             (void) StopLoadGameTimer();
11599             pausing = TRUE;
11600             ModeHighlight();
11601             break;
11602           case BeginningOfGame:
11603             if (appData.icsActive) return;
11604             /* else fall through */
11605           case MachinePlaysWhite:
11606           case MachinePlaysBlack:
11607           case TwoMachinesPlay:
11608             if (forwardMostMove == 0)
11609               return;           /* don't pause if no one has moved */
11610             if ((gameMode == MachinePlaysWhite &&
11611                  !WhiteOnMove(forwardMostMove)) ||
11612                 (gameMode == MachinePlaysBlack &&
11613                  WhiteOnMove(forwardMostMove))) {
11614                 StopClocks();
11615             }
11616             pausing = TRUE;
11617             ModeHighlight();
11618             break;
11619         }
11620     }
11621 }
11622
11623 void
11624 EditCommentEvent()
11625 {
11626     char title[MSG_SIZ];
11627
11628     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11629       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11630     } else {
11631       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11632                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11633                parseList[currentMove - 1]);
11634     }
11635
11636     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11637 }
11638
11639
11640 void
11641 EditTagsEvent()
11642 {
11643     char *tags = PGNTags(&gameInfo);
11644     EditTagsPopUp(tags);
11645     free(tags);
11646 }
11647
11648 void
11649 AnalyzeModeEvent()
11650 {
11651     if (appData.noChessProgram || gameMode == AnalyzeMode)
11652       return;
11653
11654     if (gameMode != AnalyzeFile) {
11655         if (!appData.icsEngineAnalyze) {
11656                EditGameEvent();
11657                if (gameMode != EditGame) return;
11658         }
11659         ResurrectChessProgram();
11660         SendToProgram("analyze\n", &first);
11661         first.analyzing = TRUE;
11662         /*first.maybeThinking = TRUE;*/
11663         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11664         EngineOutputPopUp();
11665     }
11666     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11667     pausing = FALSE;
11668     ModeHighlight();
11669     SetGameInfo();
11670
11671     StartAnalysisClock();
11672     GetTimeMark(&lastNodeCountTime);
11673     lastNodeCount = 0;
11674 }
11675
11676 void
11677 AnalyzeFileEvent()
11678 {
11679     if (appData.noChessProgram || gameMode == AnalyzeFile)
11680       return;
11681
11682     if (gameMode != AnalyzeMode) {
11683         EditGameEvent();
11684         if (gameMode != EditGame) return;
11685         ResurrectChessProgram();
11686         SendToProgram("analyze\n", &first);
11687         first.analyzing = TRUE;
11688         /*first.maybeThinking = TRUE;*/
11689         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11690         EngineOutputPopUp();
11691     }
11692     gameMode = AnalyzeFile;
11693     pausing = FALSE;
11694     ModeHighlight();
11695     SetGameInfo();
11696
11697     StartAnalysisClock();
11698     GetTimeMark(&lastNodeCountTime);
11699     lastNodeCount = 0;
11700 }
11701
11702 void
11703 MachineWhiteEvent()
11704 {
11705     char buf[MSG_SIZ];
11706     char *bookHit = NULL;
11707
11708     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11709       return;
11710
11711
11712     if (gameMode == PlayFromGameFile ||
11713         gameMode == TwoMachinesPlay  ||
11714         gameMode == Training         ||
11715         gameMode == AnalyzeMode      ||
11716         gameMode == EndOfGame)
11717         EditGameEvent();
11718
11719     if (gameMode == EditPosition)
11720         EditPositionDone(TRUE);
11721
11722     if (!WhiteOnMove(currentMove)) {
11723         DisplayError(_("It is not White's turn"), 0);
11724         return;
11725     }
11726
11727     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11728       ExitAnalyzeMode();
11729
11730     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11731         gameMode == AnalyzeFile)
11732         TruncateGame();
11733
11734     ResurrectChessProgram();    /* in case it isn't running */
11735     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11736         gameMode = MachinePlaysWhite;
11737         ResetClocks();
11738     } else
11739     gameMode = MachinePlaysWhite;
11740     pausing = FALSE;
11741     ModeHighlight();
11742     SetGameInfo();
11743     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11744     DisplayTitle(buf);
11745     if (first.sendName) {
11746       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11747       SendToProgram(buf, &first);
11748     }
11749     if (first.sendTime) {
11750       if (first.useColors) {
11751         SendToProgram("black\n", &first); /*gnu kludge*/
11752       }
11753       SendTimeRemaining(&first, TRUE);
11754     }
11755     if (first.useColors) {
11756       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11757     }
11758     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11759     SetMachineThinkingEnables();
11760     first.maybeThinking = TRUE;
11761     StartClocks();
11762     firstMove = FALSE;
11763
11764     if (appData.autoFlipView && !flipView) {
11765       flipView = !flipView;
11766       DrawPosition(FALSE, NULL);
11767       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11768     }
11769
11770     if(bookHit) { // [HGM] book: simulate book reply
11771         static char bookMove[MSG_SIZ]; // a bit generous?
11772
11773         programStats.nodes = programStats.depth = programStats.time =
11774         programStats.score = programStats.got_only_move = 0;
11775         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11776
11777         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11778         strcat(bookMove, bookHit);
11779         HandleMachineMove(bookMove, &first);
11780     }
11781 }
11782
11783 void
11784 MachineBlackEvent()
11785 {
11786   char buf[MSG_SIZ];
11787   char *bookHit = NULL;
11788
11789     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11790         return;
11791
11792
11793     if (gameMode == PlayFromGameFile ||
11794         gameMode == TwoMachinesPlay  ||
11795         gameMode == Training         ||
11796         gameMode == AnalyzeMode      ||
11797         gameMode == EndOfGame)
11798         EditGameEvent();
11799
11800     if (gameMode == EditPosition)
11801         EditPositionDone(TRUE);
11802
11803     if (WhiteOnMove(currentMove)) {
11804         DisplayError(_("It is not Black's turn"), 0);
11805         return;
11806     }
11807
11808     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11809       ExitAnalyzeMode();
11810
11811     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11812         gameMode == AnalyzeFile)
11813         TruncateGame();
11814
11815     ResurrectChessProgram();    /* in case it isn't running */
11816     gameMode = MachinePlaysBlack;
11817     pausing = FALSE;
11818     ModeHighlight();
11819     SetGameInfo();
11820     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11821     DisplayTitle(buf);
11822     if (first.sendName) {
11823       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11824       SendToProgram(buf, &first);
11825     }
11826     if (first.sendTime) {
11827       if (first.useColors) {
11828         SendToProgram("white\n", &first); /*gnu kludge*/
11829       }
11830       SendTimeRemaining(&first, FALSE);
11831     }
11832     if (first.useColors) {
11833       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11834     }
11835     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11836     SetMachineThinkingEnables();
11837     first.maybeThinking = TRUE;
11838     StartClocks();
11839
11840     if (appData.autoFlipView && flipView) {
11841       flipView = !flipView;
11842       DrawPosition(FALSE, NULL);
11843       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11844     }
11845     if(bookHit) { // [HGM] book: simulate book reply
11846         static char bookMove[MSG_SIZ]; // a bit generous?
11847
11848         programStats.nodes = programStats.depth = programStats.time =
11849         programStats.score = programStats.got_only_move = 0;
11850         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11851
11852         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11853         strcat(bookMove, bookHit);
11854         HandleMachineMove(bookMove, &first);
11855     }
11856 }
11857
11858
11859 void
11860 DisplayTwoMachinesTitle()
11861 {
11862     char buf[MSG_SIZ];
11863     if (appData.matchGames > 0) {
11864         if (first.twoMachinesColor[0] == 'w') {
11865           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11866                    gameInfo.white, gameInfo.black,
11867                    first.matchWins, second.matchWins,
11868                    matchGame - 1 - (first.matchWins + second.matchWins));
11869         } else {
11870           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11871                    gameInfo.white, gameInfo.black,
11872                    second.matchWins, first.matchWins,
11873                    matchGame - 1 - (first.matchWins + second.matchWins));
11874         }
11875     } else {
11876       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11877     }
11878     DisplayTitle(buf);
11879 }
11880
11881 void
11882 SettingsMenuIfReady()
11883 {
11884   if (second.lastPing != second.lastPong) {
11885     DisplayMessage("", _("Waiting for second chess program"));
11886     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
11887     return;
11888   }
11889   ThawUI();
11890   DisplayMessage("", "");
11891   SettingsPopUp(&second);
11892 }
11893
11894 int
11895 WaitForSecond(DelayedEventCallback retry)
11896 {
11897     if (second.pr == NULL) {
11898         StartChessProgram(&second);
11899         if (second.protocolVersion == 1) {
11900           retry();
11901         } else {
11902           /* kludge: allow timeout for initial "feature" command */
11903           FreezeUI();
11904           DisplayMessage("", _("Starting second chess program"));
11905           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
11906         }
11907         return 1;
11908     }
11909     return 0;
11910 }
11911
11912 void
11913 TwoMachinesEvent P((void))
11914 {
11915     int i;
11916     char buf[MSG_SIZ];
11917     ChessProgramState *onmove;
11918     char *bookHit = NULL;
11919
11920     if (appData.noChessProgram) return;
11921
11922     switch (gameMode) {
11923       case TwoMachinesPlay:
11924         return;
11925       case MachinePlaysWhite:
11926       case MachinePlaysBlack:
11927         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11928             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11929             return;
11930         }
11931         /* fall through */
11932       case BeginningOfGame:
11933       case PlayFromGameFile:
11934       case EndOfGame:
11935         EditGameEvent();
11936         if (gameMode != EditGame) return;
11937         break;
11938       case EditPosition:
11939         EditPositionDone(TRUE);
11940         break;
11941       case AnalyzeMode:
11942       case AnalyzeFile:
11943         ExitAnalyzeMode();
11944         break;
11945       case EditGame:
11946       default:
11947         break;
11948     }
11949
11950 //    forwardMostMove = currentMove;
11951     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11952     ResurrectChessProgram();    /* in case first program isn't running */
11953
11954     if(WaitForSecond(TwoMachinesEventIfReady)) return;
11955     DisplayMessage("", "");
11956     InitChessProgram(&second, FALSE);
11957     SendToProgram("force\n", &second);
11958     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
11959       ScheduleDelayedEvent(TwoMachinesEvent, 10);
11960       return;
11961     }
11962     if (startedFromSetupPosition) {
11963         SendBoard(&second, backwardMostMove);
11964     if (appData.debugMode) {
11965         fprintf(debugFP, "Two Machines\n");
11966     }
11967     }
11968     for (i = backwardMostMove; i < forwardMostMove; i++) {
11969         SendMoveToProgram(i, &second);
11970     }
11971
11972     gameMode = TwoMachinesPlay;
11973     pausing = FALSE;
11974     ModeHighlight();
11975     SetGameInfo();
11976     DisplayTwoMachinesTitle();
11977     firstMove = TRUE;
11978     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11979         onmove = &first;
11980     } else {
11981         onmove = &second;
11982     }
11983
11984     SendToProgram(first.computerString, &first);
11985     if (first.sendName) {
11986       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11987       SendToProgram(buf, &first);
11988     }
11989     SendToProgram(second.computerString, &second);
11990     if (second.sendName) {
11991       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11992       SendToProgram(buf, &second);
11993     }
11994
11995     ResetClocks();
11996     if (!first.sendTime || !second.sendTime) {
11997         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11998         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11999     }
12000     if (onmove->sendTime) {
12001       if (onmove->useColors) {
12002         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12003       }
12004       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12005     }
12006     if (onmove->useColors) {
12007       SendToProgram(onmove->twoMachinesColor, onmove);
12008     }
12009     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12010 //    SendToProgram("go\n", onmove);
12011     onmove->maybeThinking = TRUE;
12012     SetMachineThinkingEnables();
12013
12014     StartClocks();
12015
12016     if(bookHit) { // [HGM] book: simulate book reply
12017         static char bookMove[MSG_SIZ]; // a bit generous?
12018
12019         programStats.nodes = programStats.depth = programStats.time =
12020         programStats.score = programStats.got_only_move = 0;
12021         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12022
12023         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12024         strcat(bookMove, bookHit);
12025         savedMessage = bookMove; // args for deferred call
12026         savedState = onmove;
12027         ScheduleDelayedEvent(DeferredBookMove, 1);
12028     }
12029 }
12030
12031 void
12032 TrainingEvent()
12033 {
12034     if (gameMode == Training) {
12035       SetTrainingModeOff();
12036       gameMode = PlayFromGameFile;
12037       DisplayMessage("", _("Training mode off"));
12038     } else {
12039       gameMode = Training;
12040       animateTraining = appData.animate;
12041
12042       /* make sure we are not already at the end of the game */
12043       if (currentMove < forwardMostMove) {
12044         SetTrainingModeOn();
12045         DisplayMessage("", _("Training mode on"));
12046       } else {
12047         gameMode = PlayFromGameFile;
12048         DisplayError(_("Already at end of game"), 0);
12049       }
12050     }
12051     ModeHighlight();
12052 }
12053
12054 void
12055 IcsClientEvent()
12056 {
12057     if (!appData.icsActive) return;
12058     switch (gameMode) {
12059       case IcsPlayingWhite:
12060       case IcsPlayingBlack:
12061       case IcsObserving:
12062       case IcsIdle:
12063       case BeginningOfGame:
12064       case IcsExamining:
12065         return;
12066
12067       case EditGame:
12068         break;
12069
12070       case EditPosition:
12071         EditPositionDone(TRUE);
12072         break;
12073
12074       case AnalyzeMode:
12075       case AnalyzeFile:
12076         ExitAnalyzeMode();
12077         break;
12078
12079       default:
12080         EditGameEvent();
12081         break;
12082     }
12083
12084     gameMode = IcsIdle;
12085     ModeHighlight();
12086     return;
12087 }
12088
12089
12090 void
12091 EditGameEvent()
12092 {
12093     int i;
12094
12095     switch (gameMode) {
12096       case Training:
12097         SetTrainingModeOff();
12098         break;
12099       case MachinePlaysWhite:
12100       case MachinePlaysBlack:
12101       case BeginningOfGame:
12102         SendToProgram("force\n", &first);
12103         SetUserThinkingEnables();
12104         break;
12105       case PlayFromGameFile:
12106         (void) StopLoadGameTimer();
12107         if (gameFileFP != NULL) {
12108             gameFileFP = NULL;
12109         }
12110         break;
12111       case EditPosition:
12112         EditPositionDone(TRUE);
12113         break;
12114       case AnalyzeMode:
12115       case AnalyzeFile:
12116         ExitAnalyzeMode();
12117         SendToProgram("force\n", &first);
12118         break;
12119       case TwoMachinesPlay:
12120         GameEnds(EndOfFile, NULL, GE_PLAYER);
12121         ResurrectChessProgram();
12122         SetUserThinkingEnables();
12123         break;
12124       case EndOfGame:
12125         ResurrectChessProgram();
12126         break;
12127       case IcsPlayingBlack:
12128       case IcsPlayingWhite:
12129         DisplayError(_("Warning: You are still playing a game"), 0);
12130         break;
12131       case IcsObserving:
12132         DisplayError(_("Warning: You are still observing a game"), 0);
12133         break;
12134       case IcsExamining:
12135         DisplayError(_("Warning: You are still examining a game"), 0);
12136         break;
12137       case IcsIdle:
12138         break;
12139       case EditGame:
12140       default:
12141         return;
12142     }
12143
12144     pausing = FALSE;
12145     StopClocks();
12146     first.offeredDraw = second.offeredDraw = 0;
12147
12148     if (gameMode == PlayFromGameFile) {
12149         whiteTimeRemaining = timeRemaining[0][currentMove];
12150         blackTimeRemaining = timeRemaining[1][currentMove];
12151         DisplayTitle("");
12152     }
12153
12154     if (gameMode == MachinePlaysWhite ||
12155         gameMode == MachinePlaysBlack ||
12156         gameMode == TwoMachinesPlay ||
12157         gameMode == EndOfGame) {
12158         i = forwardMostMove;
12159         while (i > currentMove) {
12160             SendToProgram("undo\n", &first);
12161             i--;
12162         }
12163         whiteTimeRemaining = timeRemaining[0][currentMove];
12164         blackTimeRemaining = timeRemaining[1][currentMove];
12165         DisplayBothClocks();
12166         if (whiteFlag || blackFlag) {
12167             whiteFlag = blackFlag = 0;
12168         }
12169         DisplayTitle("");
12170     }
12171
12172     gameMode = EditGame;
12173     ModeHighlight();
12174     SetGameInfo();
12175 }
12176
12177
12178 void
12179 EditPositionEvent()
12180 {
12181     if (gameMode == EditPosition) {
12182         EditGameEvent();
12183         return;
12184     }
12185
12186     EditGameEvent();
12187     if (gameMode != EditGame) return;
12188
12189     gameMode = EditPosition;
12190     ModeHighlight();
12191     SetGameInfo();
12192     if (currentMove > 0)
12193       CopyBoard(boards[0], boards[currentMove]);
12194
12195     blackPlaysFirst = !WhiteOnMove(currentMove);
12196     ResetClocks();
12197     currentMove = forwardMostMove = backwardMostMove = 0;
12198     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12199     DisplayMove(-1);
12200 }
12201
12202 void
12203 ExitAnalyzeMode()
12204 {
12205     /* [DM] icsEngineAnalyze - possible call from other functions */
12206     if (appData.icsEngineAnalyze) {
12207         appData.icsEngineAnalyze = FALSE;
12208
12209         DisplayMessage("",_("Close ICS engine analyze..."));
12210     }
12211     if (first.analysisSupport && first.analyzing) {
12212       SendToProgram("exit\n", &first);
12213       first.analyzing = FALSE;
12214     }
12215     thinkOutput[0] = NULLCHAR;
12216 }
12217
12218 void
12219 EditPositionDone(Boolean fakeRights)
12220 {
12221     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12222
12223     startedFromSetupPosition = TRUE;
12224     InitChessProgram(&first, FALSE);
12225     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12226       boards[0][EP_STATUS] = EP_NONE;
12227       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12228     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12229         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12230         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12231       } else boards[0][CASTLING][2] = NoRights;
12232     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12233         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12234         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12235       } else boards[0][CASTLING][5] = NoRights;
12236     }
12237     SendToProgram("force\n", &first);
12238     if (blackPlaysFirst) {
12239         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12240         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12241         currentMove = forwardMostMove = backwardMostMove = 1;
12242         CopyBoard(boards[1], boards[0]);
12243     } else {
12244         currentMove = forwardMostMove = backwardMostMove = 0;
12245     }
12246     SendBoard(&first, forwardMostMove);
12247     if (appData.debugMode) {
12248         fprintf(debugFP, "EditPosDone\n");
12249     }
12250     DisplayTitle("");
12251     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12252     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12253     gameMode = EditGame;
12254     ModeHighlight();
12255     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12256     ClearHighlights(); /* [AS] */
12257 }
12258
12259 /* Pause for `ms' milliseconds */
12260 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12261 void
12262 TimeDelay(ms)
12263      long ms;
12264 {
12265     TimeMark m1, m2;
12266
12267     GetTimeMark(&m1);
12268     do {
12269         GetTimeMark(&m2);
12270     } while (SubtractTimeMarks(&m2, &m1) < ms);
12271 }
12272
12273 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12274 void
12275 SendMultiLineToICS(buf)
12276      char *buf;
12277 {
12278     char temp[MSG_SIZ+1], *p;
12279     int len;
12280
12281     len = strlen(buf);
12282     if (len > MSG_SIZ)
12283       len = MSG_SIZ;
12284
12285     strncpy(temp, buf, len);
12286     temp[len] = 0;
12287
12288     p = temp;
12289     while (*p) {
12290         if (*p == '\n' || *p == '\r')
12291           *p = ' ';
12292         ++p;
12293     }
12294
12295     strcat(temp, "\n");
12296     SendToICS(temp);
12297     SendToPlayer(temp, strlen(temp));
12298 }
12299
12300 void
12301 SetWhiteToPlayEvent()
12302 {
12303     if (gameMode == EditPosition) {
12304         blackPlaysFirst = FALSE;
12305         DisplayBothClocks();    /* works because currentMove is 0 */
12306     } else if (gameMode == IcsExamining) {
12307         SendToICS(ics_prefix);
12308         SendToICS("tomove white\n");
12309     }
12310 }
12311
12312 void
12313 SetBlackToPlayEvent()
12314 {
12315     if (gameMode == EditPosition) {
12316         blackPlaysFirst = TRUE;
12317         currentMove = 1;        /* kludge */
12318         DisplayBothClocks();
12319         currentMove = 0;
12320     } else if (gameMode == IcsExamining) {
12321         SendToICS(ics_prefix);
12322         SendToICS("tomove black\n");
12323     }
12324 }
12325
12326 void
12327 EditPositionMenuEvent(selection, x, y)
12328      ChessSquare selection;
12329      int x, y;
12330 {
12331     char buf[MSG_SIZ];
12332     ChessSquare piece = boards[0][y][x];
12333
12334     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12335
12336     switch (selection) {
12337       case ClearBoard:
12338         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12339             SendToICS(ics_prefix);
12340             SendToICS("bsetup clear\n");
12341         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12342             SendToICS(ics_prefix);
12343             SendToICS("clearboard\n");
12344         } else {
12345             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12346                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12347                 for (y = 0; y < BOARD_HEIGHT; y++) {
12348                     if (gameMode == IcsExamining) {
12349                         if (boards[currentMove][y][x] != EmptySquare) {
12350                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12351                                     AAA + x, ONE + y);
12352                             SendToICS(buf);
12353                         }
12354                     } else {
12355                         boards[0][y][x] = p;
12356                     }
12357                 }
12358             }
12359         }
12360         if (gameMode == EditPosition) {
12361             DrawPosition(FALSE, boards[0]);
12362         }
12363         break;
12364
12365       case WhitePlay:
12366         SetWhiteToPlayEvent();
12367         break;
12368
12369       case BlackPlay:
12370         SetBlackToPlayEvent();
12371         break;
12372
12373       case EmptySquare:
12374         if (gameMode == IcsExamining) {
12375             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12376             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12377             SendToICS(buf);
12378         } else {
12379             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12380                 if(x == BOARD_LEFT-2) {
12381                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12382                     boards[0][y][1] = 0;
12383                 } else
12384                 if(x == BOARD_RGHT+1) {
12385                     if(y >= gameInfo.holdingsSize) break;
12386                     boards[0][y][BOARD_WIDTH-2] = 0;
12387                 } else break;
12388             }
12389             boards[0][y][x] = EmptySquare;
12390             DrawPosition(FALSE, boards[0]);
12391         }
12392         break;
12393
12394       case PromotePiece:
12395         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12396            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12397             selection = (ChessSquare) (PROMOTED piece);
12398         } else if(piece == EmptySquare) selection = WhiteSilver;
12399         else selection = (ChessSquare)((int)piece - 1);
12400         goto defaultlabel;
12401
12402       case DemotePiece:
12403         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12404            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12405             selection = (ChessSquare) (DEMOTED piece);
12406         } else if(piece == EmptySquare) selection = BlackSilver;
12407         else selection = (ChessSquare)((int)piece + 1);
12408         goto defaultlabel;
12409
12410       case WhiteQueen:
12411       case BlackQueen:
12412         if(gameInfo.variant == VariantShatranj ||
12413            gameInfo.variant == VariantXiangqi  ||
12414            gameInfo.variant == VariantCourier  ||
12415            gameInfo.variant == VariantMakruk     )
12416             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12417         goto defaultlabel;
12418
12419       case WhiteKing:
12420       case BlackKing:
12421         if(gameInfo.variant == VariantXiangqi)
12422             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12423         if(gameInfo.variant == VariantKnightmate)
12424             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12425       default:
12426         defaultlabel:
12427         if (gameMode == IcsExamining) {
12428             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12429             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12430                      PieceToChar(selection), AAA + x, ONE + y);
12431             SendToICS(buf);
12432         } else {
12433             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12434                 int n;
12435                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12436                     n = PieceToNumber(selection - BlackPawn);
12437                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12438                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12439                     boards[0][BOARD_HEIGHT-1-n][1]++;
12440                 } else
12441                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12442                     n = PieceToNumber(selection);
12443                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12444                     boards[0][n][BOARD_WIDTH-1] = selection;
12445                     boards[0][n][BOARD_WIDTH-2]++;
12446                 }
12447             } else
12448             boards[0][y][x] = selection;
12449             DrawPosition(TRUE, boards[0]);
12450         }
12451         break;
12452     }
12453 }
12454
12455
12456 void
12457 DropMenuEvent(selection, x, y)
12458      ChessSquare selection;
12459      int x, y;
12460 {
12461     ChessMove moveType;
12462
12463     switch (gameMode) {
12464       case IcsPlayingWhite:
12465       case MachinePlaysBlack:
12466         if (!WhiteOnMove(currentMove)) {
12467             DisplayMoveError(_("It is Black's turn"));
12468             return;
12469         }
12470         moveType = WhiteDrop;
12471         break;
12472       case IcsPlayingBlack:
12473       case MachinePlaysWhite:
12474         if (WhiteOnMove(currentMove)) {
12475             DisplayMoveError(_("It is White's turn"));
12476             return;
12477         }
12478         moveType = BlackDrop;
12479         break;
12480       case EditGame:
12481         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12482         break;
12483       default:
12484         return;
12485     }
12486
12487     if (moveType == BlackDrop && selection < BlackPawn) {
12488       selection = (ChessSquare) ((int) selection
12489                                  + (int) BlackPawn - (int) WhitePawn);
12490     }
12491     if (boards[currentMove][y][x] != EmptySquare) {
12492         DisplayMoveError(_("That square is occupied"));
12493         return;
12494     }
12495
12496     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12497 }
12498
12499 void
12500 AcceptEvent()
12501 {
12502     /* Accept a pending offer of any kind from opponent */
12503
12504     if (appData.icsActive) {
12505         SendToICS(ics_prefix);
12506         SendToICS("accept\n");
12507     } else if (cmailMsgLoaded) {
12508         if (currentMove == cmailOldMove &&
12509             commentList[cmailOldMove] != NULL &&
12510             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12511                    "Black offers a draw" : "White offers a draw")) {
12512             TruncateGame();
12513             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12514             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12515         } else {
12516             DisplayError(_("There is no pending offer on this move"), 0);
12517             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12518         }
12519     } else {
12520         /* Not used for offers from chess program */
12521     }
12522 }
12523
12524 void
12525 DeclineEvent()
12526 {
12527     /* Decline a pending offer of any kind from opponent */
12528
12529     if (appData.icsActive) {
12530         SendToICS(ics_prefix);
12531         SendToICS("decline\n");
12532     } else if (cmailMsgLoaded) {
12533         if (currentMove == cmailOldMove &&
12534             commentList[cmailOldMove] != NULL &&
12535             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12536                    "Black offers a draw" : "White offers a draw")) {
12537 #ifdef NOTDEF
12538             AppendComment(cmailOldMove, "Draw declined", TRUE);
12539             DisplayComment(cmailOldMove - 1, "Draw declined");
12540 #endif /*NOTDEF*/
12541         } else {
12542             DisplayError(_("There is no pending offer on this move"), 0);
12543         }
12544     } else {
12545         /* Not used for offers from chess program */
12546     }
12547 }
12548
12549 void
12550 RematchEvent()
12551 {
12552     /* Issue ICS rematch command */
12553     if (appData.icsActive) {
12554         SendToICS(ics_prefix);
12555         SendToICS("rematch\n");
12556     }
12557 }
12558
12559 void
12560 CallFlagEvent()
12561 {
12562     /* Call your opponent's flag (claim a win on time) */
12563     if (appData.icsActive) {
12564         SendToICS(ics_prefix);
12565         SendToICS("flag\n");
12566     } else {
12567         switch (gameMode) {
12568           default:
12569             return;
12570           case MachinePlaysWhite:
12571             if (whiteFlag) {
12572                 if (blackFlag)
12573                   GameEnds(GameIsDrawn, "Both players ran out of time",
12574                            GE_PLAYER);
12575                 else
12576                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12577             } else {
12578                 DisplayError(_("Your opponent is not out of time"), 0);
12579             }
12580             break;
12581           case MachinePlaysBlack:
12582             if (blackFlag) {
12583                 if (whiteFlag)
12584                   GameEnds(GameIsDrawn, "Both players ran out of time",
12585                            GE_PLAYER);
12586                 else
12587                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12588             } else {
12589                 DisplayError(_("Your opponent is not out of time"), 0);
12590             }
12591             break;
12592         }
12593     }
12594 }
12595
12596 void
12597 DrawEvent()
12598 {
12599     /* Offer draw or accept pending draw offer from opponent */
12600
12601     if (appData.icsActive) {
12602         /* Note: tournament rules require draw offers to be
12603            made after you make your move but before you punch
12604            your clock.  Currently ICS doesn't let you do that;
12605            instead, you immediately punch your clock after making
12606            a move, but you can offer a draw at any time. */
12607
12608         SendToICS(ics_prefix);
12609         SendToICS("draw\n");
12610         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12611     } else if (cmailMsgLoaded) {
12612         if (currentMove == cmailOldMove &&
12613             commentList[cmailOldMove] != NULL &&
12614             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12615                    "Black offers a draw" : "White offers a draw")) {
12616             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12617             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12618         } else if (currentMove == cmailOldMove + 1) {
12619             char *offer = WhiteOnMove(cmailOldMove) ?
12620               "White offers a draw" : "Black offers a draw";
12621             AppendComment(currentMove, offer, TRUE);
12622             DisplayComment(currentMove - 1, offer);
12623             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12624         } else {
12625             DisplayError(_("You must make your move before offering a draw"), 0);
12626             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12627         }
12628     } else if (first.offeredDraw) {
12629         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12630     } else {
12631         if (first.sendDrawOffers) {
12632             SendToProgram("draw\n", &first);
12633             userOfferedDraw = TRUE;
12634         }
12635     }
12636 }
12637
12638 void
12639 AdjournEvent()
12640 {
12641     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12642
12643     if (appData.icsActive) {
12644         SendToICS(ics_prefix);
12645         SendToICS("adjourn\n");
12646     } else {
12647         /* Currently GNU Chess doesn't offer or accept Adjourns */
12648     }
12649 }
12650
12651
12652 void
12653 AbortEvent()
12654 {
12655     /* Offer Abort or accept pending Abort offer from opponent */
12656
12657     if (appData.icsActive) {
12658         SendToICS(ics_prefix);
12659         SendToICS("abort\n");
12660     } else {
12661         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12662     }
12663 }
12664
12665 void
12666 ResignEvent()
12667 {
12668     /* Resign.  You can do this even if it's not your turn. */
12669
12670     if (appData.icsActive) {
12671         SendToICS(ics_prefix);
12672         SendToICS("resign\n");
12673     } else {
12674         switch (gameMode) {
12675           case MachinePlaysWhite:
12676             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12677             break;
12678           case MachinePlaysBlack:
12679             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12680             break;
12681           case EditGame:
12682             if (cmailMsgLoaded) {
12683                 TruncateGame();
12684                 if (WhiteOnMove(cmailOldMove)) {
12685                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12686                 } else {
12687                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12688                 }
12689                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12690             }
12691             break;
12692           default:
12693             break;
12694         }
12695     }
12696 }
12697
12698
12699 void
12700 StopObservingEvent()
12701 {
12702     /* Stop observing current games */
12703     SendToICS(ics_prefix);
12704     SendToICS("unobserve\n");
12705 }
12706
12707 void
12708 StopExaminingEvent()
12709 {
12710     /* Stop observing current game */
12711     SendToICS(ics_prefix);
12712     SendToICS("unexamine\n");
12713 }
12714
12715 void
12716 ForwardInner(target)
12717      int target;
12718 {
12719     int limit;
12720
12721     if (appData.debugMode)
12722         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12723                 target, currentMove, forwardMostMove);
12724
12725     if (gameMode == EditPosition)
12726       return;
12727
12728     if (gameMode == PlayFromGameFile && !pausing)
12729       PauseEvent();
12730
12731     if (gameMode == IcsExamining && pausing)
12732       limit = pauseExamForwardMostMove;
12733     else
12734       limit = forwardMostMove;
12735
12736     if (target > limit) target = limit;
12737
12738     if (target > 0 && moveList[target - 1][0]) {
12739         int fromX, fromY, toX, toY;
12740         toX = moveList[target - 1][2] - AAA;
12741         toY = moveList[target - 1][3] - ONE;
12742         if (moveList[target - 1][1] == '@') {
12743             if (appData.highlightLastMove) {
12744                 SetHighlights(-1, -1, toX, toY);
12745             }
12746         } else {
12747             fromX = moveList[target - 1][0] - AAA;
12748             fromY = moveList[target - 1][1] - ONE;
12749             if (target == currentMove + 1) {
12750                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12751             }
12752             if (appData.highlightLastMove) {
12753                 SetHighlights(fromX, fromY, toX, toY);
12754             }
12755         }
12756     }
12757     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12758         gameMode == Training || gameMode == PlayFromGameFile ||
12759         gameMode == AnalyzeFile) {
12760         while (currentMove < target) {
12761             SendMoveToProgram(currentMove++, &first);
12762         }
12763     } else {
12764         currentMove = target;
12765     }
12766
12767     if (gameMode == EditGame || gameMode == EndOfGame) {
12768         whiteTimeRemaining = timeRemaining[0][currentMove];
12769         blackTimeRemaining = timeRemaining[1][currentMove];
12770     }
12771     DisplayBothClocks();
12772     DisplayMove(currentMove - 1);
12773     DrawPosition(FALSE, boards[currentMove]);
12774     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12775     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12776         DisplayComment(currentMove - 1, commentList[currentMove]);
12777     }
12778 }
12779
12780
12781 void
12782 ForwardEvent()
12783 {
12784     if (gameMode == IcsExamining && !pausing) {
12785         SendToICS(ics_prefix);
12786         SendToICS("forward\n");
12787     } else {
12788         ForwardInner(currentMove + 1);
12789     }
12790 }
12791
12792 void
12793 ToEndEvent()
12794 {
12795     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12796         /* to optimze, we temporarily turn off analysis mode while we feed
12797          * the remaining moves to the engine. Otherwise we get analysis output
12798          * after each move.
12799          */
12800         if (first.analysisSupport) {
12801           SendToProgram("exit\nforce\n", &first);
12802           first.analyzing = FALSE;
12803         }
12804     }
12805
12806     if (gameMode == IcsExamining && !pausing) {
12807         SendToICS(ics_prefix);
12808         SendToICS("forward 999999\n");
12809     } else {
12810         ForwardInner(forwardMostMove);
12811     }
12812
12813     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12814         /* we have fed all the moves, so reactivate analysis mode */
12815         SendToProgram("analyze\n", &first);
12816         first.analyzing = TRUE;
12817         /*first.maybeThinking = TRUE;*/
12818         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12819     }
12820 }
12821
12822 void
12823 BackwardInner(target)
12824      int target;
12825 {
12826     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12827
12828     if (appData.debugMode)
12829         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12830                 target, currentMove, forwardMostMove);
12831
12832     if (gameMode == EditPosition) return;
12833     if (currentMove <= backwardMostMove) {
12834         ClearHighlights();
12835         DrawPosition(full_redraw, boards[currentMove]);
12836         return;
12837     }
12838     if (gameMode == PlayFromGameFile && !pausing)
12839       PauseEvent();
12840
12841     if (moveList[target][0]) {
12842         int fromX, fromY, toX, toY;
12843         toX = moveList[target][2] - AAA;
12844         toY = moveList[target][3] - ONE;
12845         if (moveList[target][1] == '@') {
12846             if (appData.highlightLastMove) {
12847                 SetHighlights(-1, -1, toX, toY);
12848             }
12849         } else {
12850             fromX = moveList[target][0] - AAA;
12851             fromY = moveList[target][1] - ONE;
12852             if (target == currentMove - 1) {
12853                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12854             }
12855             if (appData.highlightLastMove) {
12856                 SetHighlights(fromX, fromY, toX, toY);
12857             }
12858         }
12859     }
12860     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12861         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12862         while (currentMove > target) {
12863             SendToProgram("undo\n", &first);
12864             currentMove--;
12865         }
12866     } else {
12867         currentMove = target;
12868     }
12869
12870     if (gameMode == EditGame || gameMode == EndOfGame) {
12871         whiteTimeRemaining = timeRemaining[0][currentMove];
12872         blackTimeRemaining = timeRemaining[1][currentMove];
12873     }
12874     DisplayBothClocks();
12875     DisplayMove(currentMove - 1);
12876     DrawPosition(full_redraw, boards[currentMove]);
12877     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12878     // [HGM] PV info: routine tests if comment empty
12879     DisplayComment(currentMove - 1, commentList[currentMove]);
12880 }
12881
12882 void
12883 BackwardEvent()
12884 {
12885     if (gameMode == IcsExamining && !pausing) {
12886         SendToICS(ics_prefix);
12887         SendToICS("backward\n");
12888     } else {
12889         BackwardInner(currentMove - 1);
12890     }
12891 }
12892
12893 void
12894 ToStartEvent()
12895 {
12896     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12897         /* to optimize, we temporarily turn off analysis mode while we undo
12898          * all the moves. Otherwise we get analysis output after each undo.
12899          */
12900         if (first.analysisSupport) {
12901           SendToProgram("exit\nforce\n", &first);
12902           first.analyzing = FALSE;
12903         }
12904     }
12905
12906     if (gameMode == IcsExamining && !pausing) {
12907         SendToICS(ics_prefix);
12908         SendToICS("backward 999999\n");
12909     } else {
12910         BackwardInner(backwardMostMove);
12911     }
12912
12913     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12914         /* we have fed all the moves, so reactivate analysis mode */
12915         SendToProgram("analyze\n", &first);
12916         first.analyzing = TRUE;
12917         /*first.maybeThinking = TRUE;*/
12918         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12919     }
12920 }
12921
12922 void
12923 ToNrEvent(int to)
12924 {
12925   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12926   if (to >= forwardMostMove) to = forwardMostMove;
12927   if (to <= backwardMostMove) to = backwardMostMove;
12928   if (to < currentMove) {
12929     BackwardInner(to);
12930   } else {
12931     ForwardInner(to);
12932   }
12933 }
12934
12935 void
12936 RevertEvent(Boolean annotate)
12937 {
12938     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12939         return;
12940     }
12941     if (gameMode != IcsExamining) {
12942         DisplayError(_("You are not examining a game"), 0);
12943         return;
12944     }
12945     if (pausing) {
12946         DisplayError(_("You can't revert while pausing"), 0);
12947         return;
12948     }
12949     SendToICS(ics_prefix);
12950     SendToICS("revert\n");
12951 }
12952
12953 void
12954 RetractMoveEvent()
12955 {
12956     switch (gameMode) {
12957       case MachinePlaysWhite:
12958       case MachinePlaysBlack:
12959         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12960             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12961             return;
12962         }
12963         if (forwardMostMove < 2) return;
12964         currentMove = forwardMostMove = forwardMostMove - 2;
12965         whiteTimeRemaining = timeRemaining[0][currentMove];
12966         blackTimeRemaining = timeRemaining[1][currentMove];
12967         DisplayBothClocks();
12968         DisplayMove(currentMove - 1);
12969         ClearHighlights();/*!! could figure this out*/
12970         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12971         SendToProgram("remove\n", &first);
12972         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12973         break;
12974
12975       case BeginningOfGame:
12976       default:
12977         break;
12978
12979       case IcsPlayingWhite:
12980       case IcsPlayingBlack:
12981         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12982             SendToICS(ics_prefix);
12983             SendToICS("takeback 2\n");
12984         } else {
12985             SendToICS(ics_prefix);
12986             SendToICS("takeback 1\n");
12987         }
12988         break;
12989     }
12990 }
12991
12992 void
12993 MoveNowEvent()
12994 {
12995     ChessProgramState *cps;
12996
12997     switch (gameMode) {
12998       case MachinePlaysWhite:
12999         if (!WhiteOnMove(forwardMostMove)) {
13000             DisplayError(_("It is your turn"), 0);
13001             return;
13002         }
13003         cps = &first;
13004         break;
13005       case MachinePlaysBlack:
13006         if (WhiteOnMove(forwardMostMove)) {
13007             DisplayError(_("It is your turn"), 0);
13008             return;
13009         }
13010         cps = &first;
13011         break;
13012       case TwoMachinesPlay:
13013         if (WhiteOnMove(forwardMostMove) ==
13014             (first.twoMachinesColor[0] == 'w')) {
13015             cps = &first;
13016         } else {
13017             cps = &second;
13018         }
13019         break;
13020       case BeginningOfGame:
13021       default:
13022         return;
13023     }
13024     SendToProgram("?\n", cps);
13025 }
13026
13027 void
13028 TruncateGameEvent()
13029 {
13030     EditGameEvent();
13031     if (gameMode != EditGame) return;
13032     TruncateGame();
13033 }
13034
13035 void
13036 TruncateGame()
13037 {
13038     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13039     if (forwardMostMove > currentMove) {
13040         if (gameInfo.resultDetails != NULL) {
13041             free(gameInfo.resultDetails);
13042             gameInfo.resultDetails = NULL;
13043             gameInfo.result = GameUnfinished;
13044         }
13045         forwardMostMove = currentMove;
13046         HistorySet(parseList, backwardMostMove, forwardMostMove,
13047                    currentMove-1);
13048     }
13049 }
13050
13051 void
13052 HintEvent()
13053 {
13054     if (appData.noChessProgram) return;
13055     switch (gameMode) {
13056       case MachinePlaysWhite:
13057         if (WhiteOnMove(forwardMostMove)) {
13058             DisplayError(_("Wait until your turn"), 0);
13059             return;
13060         }
13061         break;
13062       case BeginningOfGame:
13063       case MachinePlaysBlack:
13064         if (!WhiteOnMove(forwardMostMove)) {
13065             DisplayError(_("Wait until your turn"), 0);
13066             return;
13067         }
13068         break;
13069       default:
13070         DisplayError(_("No hint available"), 0);
13071         return;
13072     }
13073     SendToProgram("hint\n", &first);
13074     hintRequested = TRUE;
13075 }
13076
13077 void
13078 BookEvent()
13079 {
13080     if (appData.noChessProgram) return;
13081     switch (gameMode) {
13082       case MachinePlaysWhite:
13083         if (WhiteOnMove(forwardMostMove)) {
13084             DisplayError(_("Wait until your turn"), 0);
13085             return;
13086         }
13087         break;
13088       case BeginningOfGame:
13089       case MachinePlaysBlack:
13090         if (!WhiteOnMove(forwardMostMove)) {
13091             DisplayError(_("Wait until your turn"), 0);
13092             return;
13093         }
13094         break;
13095       case EditPosition:
13096         EditPositionDone(TRUE);
13097         break;
13098       case TwoMachinesPlay:
13099         return;
13100       default:
13101         break;
13102     }
13103     SendToProgram("bk\n", &first);
13104     bookOutput[0] = NULLCHAR;
13105     bookRequested = TRUE;
13106 }
13107
13108 void
13109 AboutGameEvent()
13110 {
13111     char *tags = PGNTags(&gameInfo);
13112     TagsPopUp(tags, CmailMsg());
13113     free(tags);
13114 }
13115
13116 /* end button procedures */
13117
13118 void
13119 PrintPosition(fp, move)
13120      FILE *fp;
13121      int move;
13122 {
13123     int i, j;
13124
13125     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13126         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13127             char c = PieceToChar(boards[move][i][j]);
13128             fputc(c == 'x' ? '.' : c, fp);
13129             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13130         }
13131     }
13132     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13133       fprintf(fp, "white to play\n");
13134     else
13135       fprintf(fp, "black to play\n");
13136 }
13137
13138 void
13139 PrintOpponents(fp)
13140      FILE *fp;
13141 {
13142     if (gameInfo.white != NULL) {
13143         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13144     } else {
13145         fprintf(fp, "\n");
13146     }
13147 }
13148
13149 /* Find last component of program's own name, using some heuristics */
13150 void
13151 TidyProgramName(prog, host, buf)
13152      char *prog, *host, buf[MSG_SIZ];
13153 {
13154     char *p, *q;
13155     int local = (strcmp(host, "localhost") == 0);
13156     while (!local && (p = strchr(prog, ';')) != NULL) {
13157         p++;
13158         while (*p == ' ') p++;
13159         prog = p;
13160     }
13161     if (*prog == '"' || *prog == '\'') {
13162         q = strchr(prog + 1, *prog);
13163     } else {
13164         q = strchr(prog, ' ');
13165     }
13166     if (q == NULL) q = prog + strlen(prog);
13167     p = q;
13168     while (p >= prog && *p != '/' && *p != '\\') p--;
13169     p++;
13170     if(p == prog && *p == '"') p++;
13171     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13172     memcpy(buf, p, q - p);
13173     buf[q - p] = NULLCHAR;
13174     if (!local) {
13175         strcat(buf, "@");
13176         strcat(buf, host);
13177     }
13178 }
13179
13180 char *
13181 TimeControlTagValue()
13182 {
13183     char buf[MSG_SIZ];
13184     if (!appData.clockMode) {
13185       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13186     } else if (movesPerSession > 0) {
13187       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13188     } else if (timeIncrement == 0) {
13189       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13190     } else {
13191       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13192     }
13193     return StrSave(buf);
13194 }
13195
13196 void
13197 SetGameInfo()
13198 {
13199     /* This routine is used only for certain modes */
13200     VariantClass v = gameInfo.variant;
13201     ChessMove r = GameUnfinished;
13202     char *p = NULL;
13203
13204     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13205         r = gameInfo.result;
13206         p = gameInfo.resultDetails;
13207         gameInfo.resultDetails = NULL;
13208     }
13209     ClearGameInfo(&gameInfo);
13210     gameInfo.variant = v;
13211
13212     switch (gameMode) {
13213       case MachinePlaysWhite:
13214         gameInfo.event = StrSave( appData.pgnEventHeader );
13215         gameInfo.site = StrSave(HostName());
13216         gameInfo.date = PGNDate();
13217         gameInfo.round = StrSave("-");
13218         gameInfo.white = StrSave(first.tidy);
13219         gameInfo.black = StrSave(UserName());
13220         gameInfo.timeControl = TimeControlTagValue();
13221         break;
13222
13223       case MachinePlaysBlack:
13224         gameInfo.event = StrSave( appData.pgnEventHeader );
13225         gameInfo.site = StrSave(HostName());
13226         gameInfo.date = PGNDate();
13227         gameInfo.round = StrSave("-");
13228         gameInfo.white = StrSave(UserName());
13229         gameInfo.black = StrSave(first.tidy);
13230         gameInfo.timeControl = TimeControlTagValue();
13231         break;
13232
13233       case TwoMachinesPlay:
13234         gameInfo.event = StrSave( appData.pgnEventHeader );
13235         gameInfo.site = StrSave(HostName());
13236         gameInfo.date = PGNDate();
13237         if (matchGame > 0) {
13238             char buf[MSG_SIZ];
13239             snprintf(buf, MSG_SIZ, "%d", matchGame);
13240             gameInfo.round = StrSave(buf);
13241         } else {
13242             gameInfo.round = StrSave("-");
13243         }
13244         if (first.twoMachinesColor[0] == 'w') {
13245             gameInfo.white = StrSave(first.tidy);
13246             gameInfo.black = StrSave(second.tidy);
13247         } else {
13248             gameInfo.white = StrSave(second.tidy);
13249             gameInfo.black = StrSave(first.tidy);
13250         }
13251         gameInfo.timeControl = TimeControlTagValue();
13252         break;
13253
13254       case EditGame:
13255         gameInfo.event = StrSave("Edited game");
13256         gameInfo.site = StrSave(HostName());
13257         gameInfo.date = PGNDate();
13258         gameInfo.round = StrSave("-");
13259         gameInfo.white = StrSave("-");
13260         gameInfo.black = StrSave("-");
13261         gameInfo.result = r;
13262         gameInfo.resultDetails = p;
13263         break;
13264
13265       case EditPosition:
13266         gameInfo.event = StrSave("Edited position");
13267         gameInfo.site = StrSave(HostName());
13268         gameInfo.date = PGNDate();
13269         gameInfo.round = StrSave("-");
13270         gameInfo.white = StrSave("-");
13271         gameInfo.black = StrSave("-");
13272         break;
13273
13274       case IcsPlayingWhite:
13275       case IcsPlayingBlack:
13276       case IcsObserving:
13277       case IcsExamining:
13278         break;
13279
13280       case PlayFromGameFile:
13281         gameInfo.event = StrSave("Game from non-PGN file");
13282         gameInfo.site = StrSave(HostName());
13283         gameInfo.date = PGNDate();
13284         gameInfo.round = StrSave("-");
13285         gameInfo.white = StrSave("?");
13286         gameInfo.black = StrSave("?");
13287         break;
13288
13289       default:
13290         break;
13291     }
13292 }
13293
13294 void
13295 ReplaceComment(index, text)
13296      int index;
13297      char *text;
13298 {
13299     int len;
13300
13301     while (*text == '\n') text++;
13302     len = strlen(text);
13303     while (len > 0 && text[len - 1] == '\n') len--;
13304
13305     if (commentList[index] != NULL)
13306       free(commentList[index]);
13307
13308     if (len == 0) {
13309         commentList[index] = NULL;
13310         return;
13311     }
13312   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13313       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13314       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13315     commentList[index] = (char *) malloc(len + 2);
13316     strncpy(commentList[index], text, len);
13317     commentList[index][len] = '\n';
13318     commentList[index][len + 1] = NULLCHAR;
13319   } else {
13320     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13321     char *p;
13322     commentList[index] = (char *) malloc(len + 7);
13323     safeStrCpy(commentList[index], "{\n", 3);
13324     safeStrCpy(commentList[index]+2, text, len+1);
13325     commentList[index][len+2] = NULLCHAR;
13326     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13327     strcat(commentList[index], "\n}\n");
13328   }
13329 }
13330
13331 void
13332 CrushCRs(text)
13333      char *text;
13334 {
13335   char *p = text;
13336   char *q = text;
13337   char ch;
13338
13339   do {
13340     ch = *p++;
13341     if (ch == '\r') continue;
13342     *q++ = ch;
13343   } while (ch != '\0');
13344 }
13345
13346 void
13347 AppendComment(index, text, addBraces)
13348      int index;
13349      char *text;
13350      Boolean addBraces; // [HGM] braces: tells if we should add {}
13351 {
13352     int oldlen, len;
13353     char *old;
13354
13355 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13356     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13357
13358     CrushCRs(text);
13359     while (*text == '\n') text++;
13360     len = strlen(text);
13361     while (len > 0 && text[len - 1] == '\n') len--;
13362
13363     if (len == 0) return;
13364
13365     if (commentList[index] != NULL) {
13366         old = commentList[index];
13367         oldlen = strlen(old);
13368         while(commentList[index][oldlen-1] ==  '\n')
13369           commentList[index][--oldlen] = NULLCHAR;
13370         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13371         safeStrCpy(commentList[index], old, oldlen + len + 6);
13372         free(old);
13373         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13374         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13375           if(addBraces) addBraces = FALSE; else { text++; len--; }
13376           while (*text == '\n') { text++; len--; }
13377           commentList[index][--oldlen] = NULLCHAR;
13378       }
13379         if(addBraces) strcat(commentList[index], "\n{\n");
13380         else          strcat(commentList[index], "\n");
13381         strcat(commentList[index], text);
13382         if(addBraces) strcat(commentList[index], "\n}\n");
13383         else          strcat(commentList[index], "\n");
13384     } else {
13385         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13386         if(addBraces)
13387           safeStrCpy(commentList[index], "{\n", 3);
13388         else commentList[index][0] = NULLCHAR;
13389         strcat(commentList[index], text);
13390         strcat(commentList[index], "\n");
13391         if(addBraces) strcat(commentList[index], "}\n");
13392     }
13393 }
13394
13395 static char * FindStr( char * text, char * sub_text )
13396 {
13397     char * result = strstr( text, sub_text );
13398
13399     if( result != NULL ) {
13400         result += strlen( sub_text );
13401     }
13402
13403     return result;
13404 }
13405
13406 /* [AS] Try to extract PV info from PGN comment */
13407 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13408 char *GetInfoFromComment( int index, char * text )
13409 {
13410     char * sep = text;
13411
13412     if( text != NULL && index > 0 ) {
13413         int score = 0;
13414         int depth = 0;
13415         int time = -1, sec = 0, deci;
13416         char * s_eval = FindStr( text, "[%eval " );
13417         char * s_emt = FindStr( text, "[%emt " );
13418
13419         if( s_eval != NULL || s_emt != NULL ) {
13420             /* New style */
13421             char delim;
13422
13423             if( s_eval != NULL ) {
13424                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13425                     return text;
13426                 }
13427
13428                 if( delim != ']' ) {
13429                     return text;
13430                 }
13431             }
13432
13433             if( s_emt != NULL ) {
13434             }
13435                 return text;
13436         }
13437         else {
13438             /* We expect something like: [+|-]nnn.nn/dd */
13439             int score_lo = 0;
13440
13441             if(*text != '{') return text; // [HGM] braces: must be normal comment
13442
13443             sep = strchr( text, '/' );
13444             if( sep == NULL || sep < (text+4) ) {
13445                 return text;
13446             }
13447
13448             time = -1; sec = -1; deci = -1;
13449             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13450                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13451                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13452                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13453                 return text;
13454             }
13455
13456             if( score_lo < 0 || score_lo >= 100 ) {
13457                 return text;
13458             }
13459
13460             if(sec >= 0) time = 600*time + 10*sec; else
13461             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13462
13463             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13464
13465             /* [HGM] PV time: now locate end of PV info */
13466             while( *++sep >= '0' && *sep <= '9'); // strip depth
13467             if(time >= 0)
13468             while( *++sep >= '0' && *sep <= '9'); // strip time
13469             if(sec >= 0)
13470             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13471             if(deci >= 0)
13472             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13473             while(*sep == ' ') sep++;
13474         }
13475
13476         if( depth <= 0 ) {
13477             return text;
13478         }
13479
13480         if( time < 0 ) {
13481             time = -1;
13482         }
13483
13484         pvInfoList[index-1].depth = depth;
13485         pvInfoList[index-1].score = score;
13486         pvInfoList[index-1].time  = 10*time; // centi-sec
13487         if(*sep == '}') *sep = 0; else *--sep = '{';
13488     }
13489     return sep;
13490 }
13491
13492 void
13493 SendToProgram(message, cps)
13494      char *message;
13495      ChessProgramState *cps;
13496 {
13497     int count, outCount, error;
13498     char buf[MSG_SIZ];
13499
13500     if (cps->pr == NULL) return;
13501     Attention(cps);
13502
13503     if (appData.debugMode) {
13504         TimeMark now;
13505         GetTimeMark(&now);
13506         fprintf(debugFP, "%ld >%-6s: %s",
13507                 SubtractTimeMarks(&now, &programStartTime),
13508                 cps->which, message);
13509     }
13510
13511     count = strlen(message);
13512     outCount = OutputToProcess(cps->pr, message, count, &error);
13513     if (outCount < count && !exiting
13514                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13515       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13516         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13517             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13518                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13519                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13520             } else {
13521                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13522             }
13523             gameInfo.resultDetails = StrSave(buf);
13524         }
13525         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13526     }
13527 }
13528
13529 void
13530 ReceiveFromProgram(isr, closure, message, count, error)
13531      InputSourceRef isr;
13532      VOIDSTAR closure;
13533      char *message;
13534      int count;
13535      int error;
13536 {
13537     char *end_str;
13538     char buf[MSG_SIZ];
13539     ChessProgramState *cps = (ChessProgramState *)closure;
13540
13541     if (isr != cps->isr) return; /* Killed intentionally */
13542     if (count <= 0) {
13543         if (count == 0) {
13544             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13545                     cps->which, cps->program);
13546         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13547                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13548                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13549                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13550                 } else {
13551                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13552                 }
13553                 gameInfo.resultDetails = StrSave(buf);
13554             }
13555             RemoveInputSource(cps->isr);
13556             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13557         } else {
13558             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13559                     cps->which, cps->program);
13560             RemoveInputSource(cps->isr);
13561
13562             /* [AS] Program is misbehaving badly... kill it */
13563             if( count == -2 ) {
13564                 DestroyChildProcess( cps->pr, 9 );
13565                 cps->pr = NoProc;
13566             }
13567
13568             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13569         }
13570         return;
13571     }
13572
13573     if ((end_str = strchr(message, '\r')) != NULL)
13574       *end_str = NULLCHAR;
13575     if ((end_str = strchr(message, '\n')) != NULL)
13576       *end_str = NULLCHAR;
13577
13578     if (appData.debugMode) {
13579         TimeMark now; int print = 1;
13580         char *quote = ""; char c; int i;
13581
13582         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13583                 char start = message[0];
13584                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13585                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13586                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13587                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13588                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13589                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13590                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13591                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13592                    sscanf(message, "hint: %c", &c)!=1 && 
13593                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13594                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13595                     print = (appData.engineComments >= 2);
13596                 }
13597                 message[0] = start; // restore original message
13598         }
13599         if(print) {
13600                 GetTimeMark(&now);
13601                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13602                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13603                         quote,
13604                         message);
13605         }
13606     }
13607
13608     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13609     if (appData.icsEngineAnalyze) {
13610         if (strstr(message, "whisper") != NULL ||
13611              strstr(message, "kibitz") != NULL ||
13612             strstr(message, "tellics") != NULL) return;
13613     }
13614
13615     HandleMachineMove(message, cps);
13616 }
13617
13618
13619 void
13620 SendTimeControl(cps, mps, tc, inc, sd, st)
13621      ChessProgramState *cps;
13622      int mps, inc, sd, st;
13623      long tc;
13624 {
13625     char buf[MSG_SIZ];
13626     int seconds;
13627
13628     if( timeControl_2 > 0 ) {
13629         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13630             tc = timeControl_2;
13631         }
13632     }
13633     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13634     inc /= cps->timeOdds;
13635     st  /= cps->timeOdds;
13636
13637     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13638
13639     if (st > 0) {
13640       /* Set exact time per move, normally using st command */
13641       if (cps->stKludge) {
13642         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13643         seconds = st % 60;
13644         if (seconds == 0) {
13645           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13646         } else {
13647           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13648         }
13649       } else {
13650         snprintf(buf, MSG_SIZ, "st %d\n", st);
13651       }
13652     } else {
13653       /* Set conventional or incremental time control, using level command */
13654       if (seconds == 0) {
13655         /* Note old gnuchess bug -- minutes:seconds used to not work.
13656            Fixed in later versions, but still avoid :seconds
13657            when seconds is 0. */
13658         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13659       } else {
13660         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13661                  seconds, inc/1000.);
13662       }
13663     }
13664     SendToProgram(buf, cps);
13665
13666     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13667     /* Orthogonally, limit search to given depth */
13668     if (sd > 0) {
13669       if (cps->sdKludge) {
13670         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13671       } else {
13672         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13673       }
13674       SendToProgram(buf, cps);
13675     }
13676
13677     if(cps->nps > 0) { /* [HGM] nps */
13678         if(cps->supportsNPS == FALSE)
13679           cps->nps = -1; // don't use if engine explicitly says not supported!
13680         else {
13681           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13682           SendToProgram(buf, cps);
13683         }
13684     }
13685 }
13686
13687 ChessProgramState *WhitePlayer()
13688 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13689 {
13690     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13691        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13692         return &second;
13693     return &first;
13694 }
13695
13696 void
13697 SendTimeRemaining(cps, machineWhite)
13698      ChessProgramState *cps;
13699      int /*boolean*/ machineWhite;
13700 {
13701     char message[MSG_SIZ];
13702     long time, otime;
13703
13704     /* Note: this routine must be called when the clocks are stopped
13705        or when they have *just* been set or switched; otherwise
13706        it will be off by the time since the current tick started.
13707     */
13708     if (machineWhite) {
13709         time = whiteTimeRemaining / 10;
13710         otime = blackTimeRemaining / 10;
13711     } else {
13712         time = blackTimeRemaining / 10;
13713         otime = whiteTimeRemaining / 10;
13714     }
13715     /* [HGM] translate opponent's time by time-odds factor */
13716     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13717     if (appData.debugMode) {
13718         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13719     }
13720
13721     if (time <= 0) time = 1;
13722     if (otime <= 0) otime = 1;
13723
13724     snprintf(message, MSG_SIZ, "time %ld\n", time);
13725     SendToProgram(message, cps);
13726
13727     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13728     SendToProgram(message, cps);
13729 }
13730
13731 int
13732 BoolFeature(p, name, loc, cps)
13733      char **p;
13734      char *name;
13735      int *loc;
13736      ChessProgramState *cps;
13737 {
13738   char buf[MSG_SIZ];
13739   int len = strlen(name);
13740   int val;
13741
13742   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13743     (*p) += len + 1;
13744     sscanf(*p, "%d", &val);
13745     *loc = (val != 0);
13746     while (**p && **p != ' ')
13747       (*p)++;
13748     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13749     SendToProgram(buf, cps);
13750     return TRUE;
13751   }
13752   return FALSE;
13753 }
13754
13755 int
13756 IntFeature(p, name, loc, cps)
13757      char **p;
13758      char *name;
13759      int *loc;
13760      ChessProgramState *cps;
13761 {
13762   char buf[MSG_SIZ];
13763   int len = strlen(name);
13764   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13765     (*p) += len + 1;
13766     sscanf(*p, "%d", loc);
13767     while (**p && **p != ' ') (*p)++;
13768     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13769     SendToProgram(buf, cps);
13770     return TRUE;
13771   }
13772   return FALSE;
13773 }
13774
13775 int
13776 StringFeature(p, name, loc, cps)
13777      char **p;
13778      char *name;
13779      char loc[];
13780      ChessProgramState *cps;
13781 {
13782   char buf[MSG_SIZ];
13783   int len = strlen(name);
13784   if (strncmp((*p), name, len) == 0
13785       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13786     (*p) += len + 2;
13787     sscanf(*p, "%[^\"]", loc);
13788     while (**p && **p != '\"') (*p)++;
13789     if (**p == '\"') (*p)++;
13790     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13791     SendToProgram(buf, cps);
13792     return TRUE;
13793   }
13794   return FALSE;
13795 }
13796
13797 int
13798 ParseOption(Option *opt, ChessProgramState *cps)
13799 // [HGM] options: process the string that defines an engine option, and determine
13800 // name, type, default value, and allowed value range
13801 {
13802         char *p, *q, buf[MSG_SIZ];
13803         int n, min = (-1)<<31, max = 1<<31, def;
13804
13805         if(p = strstr(opt->name, " -spin ")) {
13806             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13807             if(max < min) max = min; // enforce consistency
13808             if(def < min) def = min;
13809             if(def > max) def = max;
13810             opt->value = def;
13811             opt->min = min;
13812             opt->max = max;
13813             opt->type = Spin;
13814         } else if((p = strstr(opt->name, " -slider "))) {
13815             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13816             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13817             if(max < min) max = min; // enforce consistency
13818             if(def < min) def = min;
13819             if(def > max) def = max;
13820             opt->value = def;
13821             opt->min = min;
13822             opt->max = max;
13823             opt->type = Spin; // Slider;
13824         } else if((p = strstr(opt->name, " -string "))) {
13825             opt->textValue = p+9;
13826             opt->type = TextBox;
13827         } else if((p = strstr(opt->name, " -file "))) {
13828             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13829             opt->textValue = p+7;
13830             opt->type = TextBox; // FileName;
13831         } else if((p = strstr(opt->name, " -path "))) {
13832             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13833             opt->textValue = p+7;
13834             opt->type = TextBox; // PathName;
13835         } else if(p = strstr(opt->name, " -check ")) {
13836             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13837             opt->value = (def != 0);
13838             opt->type = CheckBox;
13839         } else if(p = strstr(opt->name, " -combo ")) {
13840             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13841             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13842             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13843             opt->value = n = 0;
13844             while(q = StrStr(q, " /// ")) {
13845                 n++; *q = 0;    // count choices, and null-terminate each of them
13846                 q += 5;
13847                 if(*q == '*') { // remember default, which is marked with * prefix
13848                     q++;
13849                     opt->value = n;
13850                 }
13851                 cps->comboList[cps->comboCnt++] = q;
13852             }
13853             cps->comboList[cps->comboCnt++] = NULL;
13854             opt->max = n + 1;
13855             opt->type = ComboBox;
13856         } else if(p = strstr(opt->name, " -button")) {
13857             opt->type = Button;
13858         } else if(p = strstr(opt->name, " -save")) {
13859             opt->type = SaveButton;
13860         } else return FALSE;
13861         *p = 0; // terminate option name
13862         // now look if the command-line options define a setting for this engine option.
13863         if(cps->optionSettings && cps->optionSettings[0])
13864             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13865         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13866           snprintf(buf, MSG_SIZ, "option %s", p);
13867                 if(p = strstr(buf, ",")) *p = 0;
13868                 if(q = strchr(buf, '=')) switch(opt->type) {
13869                     case ComboBox:
13870                         for(n=0; n<opt->max; n++)
13871                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
13872                         break;
13873                     case TextBox:
13874                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
13875                         break;
13876                     case Spin:
13877                     case CheckBox:
13878                         opt->value = atoi(q+1);
13879                     default:
13880                         break;
13881                 }
13882                 strcat(buf, "\n");
13883                 SendToProgram(buf, cps);
13884         }
13885         return TRUE;
13886 }
13887
13888 void
13889 FeatureDone(cps, val)
13890      ChessProgramState* cps;
13891      int val;
13892 {
13893   DelayedEventCallback cb = GetDelayedEvent();
13894   if ((cb == InitBackEnd3 && cps == &first) ||
13895       (cb == SettingsMenuIfReady && cps == &second) ||
13896       (cb == TwoMachinesEventIfReady && cps == &second)) {
13897     CancelDelayedEvent();
13898     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13899   }
13900   cps->initDone = val;
13901 }
13902
13903 /* Parse feature command from engine */
13904 void
13905 ParseFeatures(args, cps)
13906      char* args;
13907      ChessProgramState *cps;
13908 {
13909   char *p = args;
13910   char *q;
13911   int val;
13912   char buf[MSG_SIZ];
13913
13914   for (;;) {
13915     while (*p == ' ') p++;
13916     if (*p == NULLCHAR) return;
13917
13918     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13919     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13920     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13921     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13922     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13923     if (BoolFeature(&p, "reuse", &val, cps)) {
13924       /* Engine can disable reuse, but can't enable it if user said no */
13925       if (!val) cps->reuse = FALSE;
13926       continue;
13927     }
13928     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13929     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13930       if (gameMode == TwoMachinesPlay) {
13931         DisplayTwoMachinesTitle();
13932       } else {
13933         DisplayTitle("");
13934       }
13935       continue;
13936     }
13937     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13938     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13939     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13940     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13941     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13942     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13943     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13944     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13945     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13946     if (IntFeature(&p, "done", &val, cps)) {
13947       FeatureDone(cps, val);
13948       continue;
13949     }
13950     /* Added by Tord: */
13951     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13952     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13953     /* End of additions by Tord */
13954
13955     /* [HGM] added features: */
13956     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13957     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13958     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13959     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13960     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13961     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13962     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13963         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13964           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13965             SendToProgram(buf, cps);
13966             continue;
13967         }
13968         if(cps->nrOptions >= MAX_OPTIONS) {
13969             cps->nrOptions--;
13970             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13971             DisplayError(buf, 0);
13972         }
13973         continue;
13974     }
13975     /* End of additions by HGM */
13976
13977     /* unknown feature: complain and skip */
13978     q = p;
13979     while (*q && *q != '=') q++;
13980     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13981     SendToProgram(buf, cps);
13982     p = q;
13983     if (*p == '=') {
13984       p++;
13985       if (*p == '\"') {
13986         p++;
13987         while (*p && *p != '\"') p++;
13988         if (*p == '\"') p++;
13989       } else {
13990         while (*p && *p != ' ') p++;
13991       }
13992     }
13993   }
13994
13995 }
13996
13997 void
13998 PeriodicUpdatesEvent(newState)
13999      int newState;
14000 {
14001     if (newState == appData.periodicUpdates)
14002       return;
14003
14004     appData.periodicUpdates=newState;
14005
14006     /* Display type changes, so update it now */
14007 //    DisplayAnalysis();
14008
14009     /* Get the ball rolling again... */
14010     if (newState) {
14011         AnalysisPeriodicEvent(1);
14012         StartAnalysisClock();
14013     }
14014 }
14015
14016 void
14017 PonderNextMoveEvent(newState)
14018      int newState;
14019 {
14020     if (newState == appData.ponderNextMove) return;
14021     if (gameMode == EditPosition) EditPositionDone(TRUE);
14022     if (newState) {
14023         SendToProgram("hard\n", &first);
14024         if (gameMode == TwoMachinesPlay) {
14025             SendToProgram("hard\n", &second);
14026         }
14027     } else {
14028         SendToProgram("easy\n", &first);
14029         thinkOutput[0] = NULLCHAR;
14030         if (gameMode == TwoMachinesPlay) {
14031             SendToProgram("easy\n", &second);
14032         }
14033     }
14034     appData.ponderNextMove = newState;
14035 }
14036
14037 void
14038 NewSettingEvent(option, feature, command, value)
14039      char *command;
14040      int option, value, *feature;
14041 {
14042     char buf[MSG_SIZ];
14043
14044     if (gameMode == EditPosition) EditPositionDone(TRUE);
14045     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14046     if(feature == NULL || *feature) SendToProgram(buf, &first);
14047     if (gameMode == TwoMachinesPlay) {
14048         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14049     }
14050 }
14051
14052 void
14053 ShowThinkingEvent()
14054 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14055 {
14056     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14057     int newState = appData.showThinking
14058         // [HGM] thinking: other features now need thinking output as well
14059         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14060
14061     if (oldState == newState) return;
14062     oldState = newState;
14063     if (gameMode == EditPosition) EditPositionDone(TRUE);
14064     if (oldState) {
14065         SendToProgram("post\n", &first);
14066         if (gameMode == TwoMachinesPlay) {
14067             SendToProgram("post\n", &second);
14068         }
14069     } else {
14070         SendToProgram("nopost\n", &first);
14071         thinkOutput[0] = NULLCHAR;
14072         if (gameMode == TwoMachinesPlay) {
14073             SendToProgram("nopost\n", &second);
14074         }
14075     }
14076 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14077 }
14078
14079 void
14080 AskQuestionEvent(title, question, replyPrefix, which)
14081      char *title; char *question; char *replyPrefix; char *which;
14082 {
14083   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14084   if (pr == NoProc) return;
14085   AskQuestion(title, question, replyPrefix, pr);
14086 }
14087
14088 void
14089 DisplayMove(moveNumber)
14090      int moveNumber;
14091 {
14092     char message[MSG_SIZ];
14093     char res[MSG_SIZ];
14094     char cpThinkOutput[MSG_SIZ];
14095
14096     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14097
14098     if (moveNumber == forwardMostMove - 1 ||
14099         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14100
14101         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14102
14103         if (strchr(cpThinkOutput, '\n')) {
14104             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14105         }
14106     } else {
14107         *cpThinkOutput = NULLCHAR;
14108     }
14109
14110     /* [AS] Hide thinking from human user */
14111     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14112         *cpThinkOutput = NULLCHAR;
14113         if( thinkOutput[0] != NULLCHAR ) {
14114             int i;
14115
14116             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14117                 cpThinkOutput[i] = '.';
14118             }
14119             cpThinkOutput[i] = NULLCHAR;
14120             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14121         }
14122     }
14123
14124     if (moveNumber == forwardMostMove - 1 &&
14125         gameInfo.resultDetails != NULL) {
14126         if (gameInfo.resultDetails[0] == NULLCHAR) {
14127           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14128         } else {
14129           snprintf(res, MSG_SIZ, " {%s} %s",
14130                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14131         }
14132     } else {
14133         res[0] = NULLCHAR;
14134     }
14135
14136     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14137         DisplayMessage(res, cpThinkOutput);
14138     } else {
14139       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14140                 WhiteOnMove(moveNumber) ? " " : ".. ",
14141                 parseList[moveNumber], res);
14142         DisplayMessage(message, cpThinkOutput);
14143     }
14144 }
14145
14146 void
14147 DisplayComment(moveNumber, text)
14148      int moveNumber;
14149      char *text;
14150 {
14151     char title[MSG_SIZ];
14152     char buf[8000]; // comment can be long!
14153     int score, depth;
14154
14155     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14156       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14157     } else {
14158       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14159               WhiteOnMove(moveNumber) ? " " : ".. ",
14160               parseList[moveNumber]);
14161     }
14162     // [HGM] PV info: display PV info together with (or as) comment
14163     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14164       if(text == NULL) text = "";
14165       score = pvInfoList[moveNumber].score;
14166       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14167               depth, (pvInfoList[moveNumber].time+50)/100, text);
14168       text = buf;
14169     }
14170     if (text != NULL && (appData.autoDisplayComment || commentUp))
14171         CommentPopUp(title, text);
14172 }
14173
14174 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14175  * might be busy thinking or pondering.  It can be omitted if your
14176  * gnuchess is configured to stop thinking immediately on any user
14177  * input.  However, that gnuchess feature depends on the FIONREAD
14178  * ioctl, which does not work properly on some flavors of Unix.
14179  */
14180 void
14181 Attention(cps)
14182      ChessProgramState *cps;
14183 {
14184 #if ATTENTION
14185     if (!cps->useSigint) return;
14186     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14187     switch (gameMode) {
14188       case MachinePlaysWhite:
14189       case MachinePlaysBlack:
14190       case TwoMachinesPlay:
14191       case IcsPlayingWhite:
14192       case IcsPlayingBlack:
14193       case AnalyzeMode:
14194       case AnalyzeFile:
14195         /* Skip if we know it isn't thinking */
14196         if (!cps->maybeThinking) return;
14197         if (appData.debugMode)
14198           fprintf(debugFP, "Interrupting %s\n", cps->which);
14199         InterruptChildProcess(cps->pr);
14200         cps->maybeThinking = FALSE;
14201         break;
14202       default:
14203         break;
14204     }
14205 #endif /*ATTENTION*/
14206 }
14207
14208 int
14209 CheckFlags()
14210 {
14211     if (whiteTimeRemaining <= 0) {
14212         if (!whiteFlag) {
14213             whiteFlag = TRUE;
14214             if (appData.icsActive) {
14215                 if (appData.autoCallFlag &&
14216                     gameMode == IcsPlayingBlack && !blackFlag) {
14217                   SendToICS(ics_prefix);
14218                   SendToICS("flag\n");
14219                 }
14220             } else {
14221                 if (blackFlag) {
14222                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14223                 } else {
14224                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14225                     if (appData.autoCallFlag) {
14226                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14227                         return TRUE;
14228                     }
14229                 }
14230             }
14231         }
14232     }
14233     if (blackTimeRemaining <= 0) {
14234         if (!blackFlag) {
14235             blackFlag = TRUE;
14236             if (appData.icsActive) {
14237                 if (appData.autoCallFlag &&
14238                     gameMode == IcsPlayingWhite && !whiteFlag) {
14239                   SendToICS(ics_prefix);
14240                   SendToICS("flag\n");
14241                 }
14242             } else {
14243                 if (whiteFlag) {
14244                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14245                 } else {
14246                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14247                     if (appData.autoCallFlag) {
14248                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14249                         return TRUE;
14250                     }
14251                 }
14252             }
14253         }
14254     }
14255     return FALSE;
14256 }
14257
14258 void
14259 CheckTimeControl()
14260 {
14261     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14262         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14263
14264     /*
14265      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14266      */
14267     if ( !WhiteOnMove(forwardMostMove) ) {
14268         /* White made time control */
14269         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14270         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14271         /* [HGM] time odds: correct new time quota for time odds! */
14272                                             / WhitePlayer()->timeOdds;
14273         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14274     } else {
14275         lastBlack -= blackTimeRemaining;
14276         /* Black made time control */
14277         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14278                                             / WhitePlayer()->other->timeOdds;
14279         lastWhite = whiteTimeRemaining;
14280     }
14281 }
14282
14283 void
14284 DisplayBothClocks()
14285 {
14286     int wom = gameMode == EditPosition ?
14287       !blackPlaysFirst : WhiteOnMove(currentMove);
14288     DisplayWhiteClock(whiteTimeRemaining, wom);
14289     DisplayBlackClock(blackTimeRemaining, !wom);
14290 }
14291
14292
14293 /* Timekeeping seems to be a portability nightmare.  I think everyone
14294    has ftime(), but I'm really not sure, so I'm including some ifdefs
14295    to use other calls if you don't.  Clocks will be less accurate if
14296    you have neither ftime nor gettimeofday.
14297 */
14298
14299 /* VS 2008 requires the #include outside of the function */
14300 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14301 #include <sys/timeb.h>
14302 #endif
14303
14304 /* Get the current time as a TimeMark */
14305 void
14306 GetTimeMark(tm)
14307      TimeMark *tm;
14308 {
14309 #if HAVE_GETTIMEOFDAY
14310
14311     struct timeval timeVal;
14312     struct timezone timeZone;
14313
14314     gettimeofday(&timeVal, &timeZone);
14315     tm->sec = (long) timeVal.tv_sec;
14316     tm->ms = (int) (timeVal.tv_usec / 1000L);
14317
14318 #else /*!HAVE_GETTIMEOFDAY*/
14319 #if HAVE_FTIME
14320
14321 // include <sys/timeb.h> / moved to just above start of function
14322     struct timeb timeB;
14323
14324     ftime(&timeB);
14325     tm->sec = (long) timeB.time;
14326     tm->ms = (int) timeB.millitm;
14327
14328 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14329     tm->sec = (long) time(NULL);
14330     tm->ms = 0;
14331 #endif
14332 #endif
14333 }
14334
14335 /* Return the difference in milliseconds between two
14336    time marks.  We assume the difference will fit in a long!
14337 */
14338 long
14339 SubtractTimeMarks(tm2, tm1)
14340      TimeMark *tm2, *tm1;
14341 {
14342     return 1000L*(tm2->sec - tm1->sec) +
14343            (long) (tm2->ms - tm1->ms);
14344 }
14345
14346
14347 /*
14348  * Code to manage the game clocks.
14349  *
14350  * In tournament play, black starts the clock and then white makes a move.
14351  * We give the human user a slight advantage if he is playing white---the
14352  * clocks don't run until he makes his first move, so it takes zero time.
14353  * Also, we don't account for network lag, so we could get out of sync
14354  * with GNU Chess's clock -- but then, referees are always right.
14355  */
14356
14357 static TimeMark tickStartTM;
14358 static long intendedTickLength;
14359
14360 long
14361 NextTickLength(timeRemaining)
14362      long timeRemaining;
14363 {
14364     long nominalTickLength, nextTickLength;
14365
14366     if (timeRemaining > 0L && timeRemaining <= 10000L)
14367       nominalTickLength = 100L;
14368     else
14369       nominalTickLength = 1000L;
14370     nextTickLength = timeRemaining % nominalTickLength;
14371     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14372
14373     return nextTickLength;
14374 }
14375
14376 /* Adjust clock one minute up or down */
14377 void
14378 AdjustClock(Boolean which, int dir)
14379 {
14380     if(which) blackTimeRemaining += 60000*dir;
14381     else      whiteTimeRemaining += 60000*dir;
14382     DisplayBothClocks();
14383 }
14384
14385 /* Stop clocks and reset to a fresh time control */
14386 void
14387 ResetClocks()
14388 {
14389     (void) StopClockTimer();
14390     if (appData.icsActive) {
14391         whiteTimeRemaining = blackTimeRemaining = 0;
14392     } else if (searchTime) {
14393         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14394         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14395     } else { /* [HGM] correct new time quote for time odds */
14396         whiteTC = blackTC = fullTimeControlString;
14397         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14398         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14399     }
14400     if (whiteFlag || blackFlag) {
14401         DisplayTitle("");
14402         whiteFlag = blackFlag = FALSE;
14403     }
14404     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14405     DisplayBothClocks();
14406 }
14407
14408 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14409
14410 /* Decrement running clock by amount of time that has passed */
14411 void
14412 DecrementClocks()
14413 {
14414     long timeRemaining;
14415     long lastTickLength, fudge;
14416     TimeMark now;
14417
14418     if (!appData.clockMode) return;
14419     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14420
14421     GetTimeMark(&now);
14422
14423     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14424
14425     /* Fudge if we woke up a little too soon */
14426     fudge = intendedTickLength - lastTickLength;
14427     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14428
14429     if (WhiteOnMove(forwardMostMove)) {
14430         if(whiteNPS >= 0) lastTickLength = 0;
14431         timeRemaining = whiteTimeRemaining -= lastTickLength;
14432         if(timeRemaining < 0 && !appData.icsActive) {
14433             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14434             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14435                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14436                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14437             }
14438         }
14439         DisplayWhiteClock(whiteTimeRemaining - fudge,
14440                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14441     } else {
14442         if(blackNPS >= 0) lastTickLength = 0;
14443         timeRemaining = blackTimeRemaining -= lastTickLength;
14444         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14445             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14446             if(suddenDeath) {
14447                 blackStartMove = forwardMostMove;
14448                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14449             }
14450         }
14451         DisplayBlackClock(blackTimeRemaining - fudge,
14452                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14453     }
14454     if (CheckFlags()) return;
14455
14456     tickStartTM = now;
14457     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14458     StartClockTimer(intendedTickLength);
14459
14460     /* if the time remaining has fallen below the alarm threshold, sound the
14461      * alarm. if the alarm has sounded and (due to a takeback or time control
14462      * with increment) the time remaining has increased to a level above the
14463      * threshold, reset the alarm so it can sound again.
14464      */
14465
14466     if (appData.icsActive && appData.icsAlarm) {
14467
14468         /* make sure we are dealing with the user's clock */
14469         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14470                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14471            )) return;
14472
14473         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14474             alarmSounded = FALSE;
14475         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14476             PlayAlarmSound();
14477             alarmSounded = TRUE;
14478         }
14479     }
14480 }
14481
14482
14483 /* A player has just moved, so stop the previously running
14484    clock and (if in clock mode) start the other one.
14485    We redisplay both clocks in case we're in ICS mode, because
14486    ICS gives us an update to both clocks after every move.
14487    Note that this routine is called *after* forwardMostMove
14488    is updated, so the last fractional tick must be subtracted
14489    from the color that is *not* on move now.
14490 */
14491 void
14492 SwitchClocks(int newMoveNr)
14493 {
14494     long lastTickLength;
14495     TimeMark now;
14496     int flagged = FALSE;
14497
14498     GetTimeMark(&now);
14499
14500     if (StopClockTimer() && appData.clockMode) {
14501         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14502         if (!WhiteOnMove(forwardMostMove)) {
14503             if(blackNPS >= 0) lastTickLength = 0;
14504             blackTimeRemaining -= lastTickLength;
14505            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14506 //         if(pvInfoList[forwardMostMove-1].time == -1)
14507                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14508                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14509         } else {
14510            if(whiteNPS >= 0) lastTickLength = 0;
14511            whiteTimeRemaining -= lastTickLength;
14512            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14513 //         if(pvInfoList[forwardMostMove-1].time == -1)
14514                  pvInfoList[forwardMostMove-1].time =
14515                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14516         }
14517         flagged = CheckFlags();
14518     }
14519     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14520     CheckTimeControl();
14521
14522     if (flagged || !appData.clockMode) return;
14523
14524     switch (gameMode) {
14525       case MachinePlaysBlack:
14526       case MachinePlaysWhite:
14527       case BeginningOfGame:
14528         if (pausing) return;
14529         break;
14530
14531       case EditGame:
14532       case PlayFromGameFile:
14533       case IcsExamining:
14534         return;
14535
14536       default:
14537         break;
14538     }
14539
14540     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14541         if(WhiteOnMove(forwardMostMove))
14542              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14543         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14544     }
14545
14546     tickStartTM = now;
14547     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14548       whiteTimeRemaining : blackTimeRemaining);
14549     StartClockTimer(intendedTickLength);
14550 }
14551
14552
14553 /* Stop both clocks */
14554 void
14555 StopClocks()
14556 {
14557     long lastTickLength;
14558     TimeMark now;
14559
14560     if (!StopClockTimer()) return;
14561     if (!appData.clockMode) return;
14562
14563     GetTimeMark(&now);
14564
14565     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14566     if (WhiteOnMove(forwardMostMove)) {
14567         if(whiteNPS >= 0) lastTickLength = 0;
14568         whiteTimeRemaining -= lastTickLength;
14569         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14570     } else {
14571         if(blackNPS >= 0) lastTickLength = 0;
14572         blackTimeRemaining -= lastTickLength;
14573         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14574     }
14575     CheckFlags();
14576 }
14577
14578 /* Start clock of player on move.  Time may have been reset, so
14579    if clock is already running, stop and restart it. */
14580 void
14581 StartClocks()
14582 {
14583     (void) StopClockTimer(); /* in case it was running already */
14584     DisplayBothClocks();
14585     if (CheckFlags()) return;
14586
14587     if (!appData.clockMode) return;
14588     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14589
14590     GetTimeMark(&tickStartTM);
14591     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14592       whiteTimeRemaining : blackTimeRemaining);
14593
14594    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14595     whiteNPS = blackNPS = -1;
14596     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14597        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14598         whiteNPS = first.nps;
14599     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14600        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14601         blackNPS = first.nps;
14602     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14603         whiteNPS = second.nps;
14604     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14605         blackNPS = second.nps;
14606     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14607
14608     StartClockTimer(intendedTickLength);
14609 }
14610
14611 char *
14612 TimeString(ms)
14613      long ms;
14614 {
14615     long second, minute, hour, day;
14616     char *sign = "";
14617     static char buf[32];
14618
14619     if (ms > 0 && ms <= 9900) {
14620       /* convert milliseconds to tenths, rounding up */
14621       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14622
14623       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14624       return buf;
14625     }
14626
14627     /* convert milliseconds to seconds, rounding up */
14628     /* use floating point to avoid strangeness of integer division
14629        with negative dividends on many machines */
14630     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14631
14632     if (second < 0) {
14633         sign = "-";
14634         second = -second;
14635     }
14636
14637     day = second / (60 * 60 * 24);
14638     second = second % (60 * 60 * 24);
14639     hour = second / (60 * 60);
14640     second = second % (60 * 60);
14641     minute = second / 60;
14642     second = second % 60;
14643
14644     if (day > 0)
14645       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14646               sign, day, hour, minute, second);
14647     else if (hour > 0)
14648       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14649     else
14650       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14651
14652     return buf;
14653 }
14654
14655
14656 /*
14657  * This is necessary because some C libraries aren't ANSI C compliant yet.
14658  */
14659 char *
14660 StrStr(string, match)
14661      char *string, *match;
14662 {
14663     int i, length;
14664
14665     length = strlen(match);
14666
14667     for (i = strlen(string) - length; i >= 0; i--, string++)
14668       if (!strncmp(match, string, length))
14669         return string;
14670
14671     return NULL;
14672 }
14673
14674 char *
14675 StrCaseStr(string, match)
14676      char *string, *match;
14677 {
14678     int i, j, length;
14679
14680     length = strlen(match);
14681
14682     for (i = strlen(string) - length; i >= 0; i--, string++) {
14683         for (j = 0; j < length; j++) {
14684             if (ToLower(match[j]) != ToLower(string[j]))
14685               break;
14686         }
14687         if (j == length) return string;
14688     }
14689
14690     return NULL;
14691 }
14692
14693 #ifndef _amigados
14694 int
14695 StrCaseCmp(s1, s2)
14696      char *s1, *s2;
14697 {
14698     char c1, c2;
14699
14700     for (;;) {
14701         c1 = ToLower(*s1++);
14702         c2 = ToLower(*s2++);
14703         if (c1 > c2) return 1;
14704         if (c1 < c2) return -1;
14705         if (c1 == NULLCHAR) return 0;
14706     }
14707 }
14708
14709
14710 int
14711 ToLower(c)
14712      int c;
14713 {
14714     return isupper(c) ? tolower(c) : c;
14715 }
14716
14717
14718 int
14719 ToUpper(c)
14720      int c;
14721 {
14722     return islower(c) ? toupper(c) : c;
14723 }
14724 #endif /* !_amigados    */
14725
14726 char *
14727 StrSave(s)
14728      char *s;
14729 {
14730   char *ret;
14731
14732   if ((ret = (char *) malloc(strlen(s) + 1)))
14733     {
14734       safeStrCpy(ret, s, strlen(s)+1);
14735     }
14736   return ret;
14737 }
14738
14739 char *
14740 StrSavePtr(s, savePtr)
14741      char *s, **savePtr;
14742 {
14743     if (*savePtr) {
14744         free(*savePtr);
14745     }
14746     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14747       safeStrCpy(*savePtr, s, strlen(s)+1);
14748     }
14749     return(*savePtr);
14750 }
14751
14752 char *
14753 PGNDate()
14754 {
14755     time_t clock;
14756     struct tm *tm;
14757     char buf[MSG_SIZ];
14758
14759     clock = time((time_t *)NULL);
14760     tm = localtime(&clock);
14761     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14762             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14763     return StrSave(buf);
14764 }
14765
14766
14767 char *
14768 PositionToFEN(move, overrideCastling)
14769      int move;
14770      char *overrideCastling;
14771 {
14772     int i, j, fromX, fromY, toX, toY;
14773     int whiteToPlay;
14774     char buf[128];
14775     char *p, *q;
14776     int emptycount;
14777     ChessSquare piece;
14778
14779     whiteToPlay = (gameMode == EditPosition) ?
14780       !blackPlaysFirst : (move % 2 == 0);
14781     p = buf;
14782
14783     /* Piece placement data */
14784     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14785         emptycount = 0;
14786         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14787             if (boards[move][i][j] == EmptySquare) {
14788                 emptycount++;
14789             } else { ChessSquare piece = boards[move][i][j];
14790                 if (emptycount > 0) {
14791                     if(emptycount<10) /* [HGM] can be >= 10 */
14792                         *p++ = '0' + emptycount;
14793                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14794                     emptycount = 0;
14795                 }
14796                 if(PieceToChar(piece) == '+') {
14797                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14798                     *p++ = '+';
14799                     piece = (ChessSquare)(DEMOTED piece);
14800                 }
14801                 *p++ = PieceToChar(piece);
14802                 if(p[-1] == '~') {
14803                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14804                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14805                     *p++ = '~';
14806                 }
14807             }
14808         }
14809         if (emptycount > 0) {
14810             if(emptycount<10) /* [HGM] can be >= 10 */
14811                 *p++ = '0' + emptycount;
14812             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14813             emptycount = 0;
14814         }
14815         *p++ = '/';
14816     }
14817     *(p - 1) = ' ';
14818
14819     /* [HGM] print Crazyhouse or Shogi holdings */
14820     if( gameInfo.holdingsWidth ) {
14821         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14822         q = p;
14823         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14824             piece = boards[move][i][BOARD_WIDTH-1];
14825             if( piece != EmptySquare )
14826               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14827                   *p++ = PieceToChar(piece);
14828         }
14829         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14830             piece = boards[move][BOARD_HEIGHT-i-1][0];
14831             if( piece != EmptySquare )
14832               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14833                   *p++ = PieceToChar(piece);
14834         }
14835
14836         if( q == p ) *p++ = '-';
14837         *p++ = ']';
14838         *p++ = ' ';
14839     }
14840
14841     /* Active color */
14842     *p++ = whiteToPlay ? 'w' : 'b';
14843     *p++ = ' ';
14844
14845   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14846     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14847   } else {
14848   if(nrCastlingRights) {
14849      q = p;
14850      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14851        /* [HGM] write directly from rights */
14852            if(boards[move][CASTLING][2] != NoRights &&
14853               boards[move][CASTLING][0] != NoRights   )
14854                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14855            if(boards[move][CASTLING][2] != NoRights &&
14856               boards[move][CASTLING][1] != NoRights   )
14857                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14858            if(boards[move][CASTLING][5] != NoRights &&
14859               boards[move][CASTLING][3] != NoRights   )
14860                 *p++ = boards[move][CASTLING][3] + AAA;
14861            if(boards[move][CASTLING][5] != NoRights &&
14862               boards[move][CASTLING][4] != NoRights   )
14863                 *p++ = boards[move][CASTLING][4] + AAA;
14864      } else {
14865
14866         /* [HGM] write true castling rights */
14867         if( nrCastlingRights == 6 ) {
14868             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14869                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14870             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14871                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14872             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14873                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14874             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14875                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14876         }
14877      }
14878      if (q == p) *p++ = '-'; /* No castling rights */
14879      *p++ = ' ';
14880   }
14881
14882   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14883      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14884     /* En passant target square */
14885     if (move > backwardMostMove) {
14886         fromX = moveList[move - 1][0] - AAA;
14887         fromY = moveList[move - 1][1] - ONE;
14888         toX = moveList[move - 1][2] - AAA;
14889         toY = moveList[move - 1][3] - ONE;
14890         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14891             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14892             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14893             fromX == toX) {
14894             /* 2-square pawn move just happened */
14895             *p++ = toX + AAA;
14896             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14897         } else {
14898             *p++ = '-';
14899         }
14900     } else if(move == backwardMostMove) {
14901         // [HGM] perhaps we should always do it like this, and forget the above?
14902         if((signed char)boards[move][EP_STATUS] >= 0) {
14903             *p++ = boards[move][EP_STATUS] + AAA;
14904             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14905         } else {
14906             *p++ = '-';
14907         }
14908     } else {
14909         *p++ = '-';
14910     }
14911     *p++ = ' ';
14912   }
14913   }
14914
14915     /* [HGM] find reversible plies */
14916     {   int i = 0, j=move;
14917
14918         if (appData.debugMode) { int k;
14919             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14920             for(k=backwardMostMove; k<=forwardMostMove; k++)
14921                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14922
14923         }
14924
14925         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14926         if( j == backwardMostMove ) i += initialRulePlies;
14927         sprintf(p, "%d ", i);
14928         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14929     }
14930     /* Fullmove number */
14931     sprintf(p, "%d", (move / 2) + 1);
14932
14933     return StrSave(buf);
14934 }
14935
14936 Boolean
14937 ParseFEN(board, blackPlaysFirst, fen)
14938     Board board;
14939      int *blackPlaysFirst;
14940      char *fen;
14941 {
14942     int i, j;
14943     char *p, c;
14944     int emptycount;
14945     ChessSquare piece;
14946
14947     p = fen;
14948
14949     /* [HGM] by default clear Crazyhouse holdings, if present */
14950     if(gameInfo.holdingsWidth) {
14951        for(i=0; i<BOARD_HEIGHT; i++) {
14952            board[i][0]             = EmptySquare; /* black holdings */
14953            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14954            board[i][1]             = (ChessSquare) 0; /* black counts */
14955            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14956        }
14957     }
14958
14959     /* Piece placement data */
14960     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14961         j = 0;
14962         for (;;) {
14963             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14964                 if (*p == '/') p++;
14965                 emptycount = gameInfo.boardWidth - j;
14966                 while (emptycount--)
14967                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14968                 break;
14969 #if(BOARD_FILES >= 10)
14970             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14971                 p++; emptycount=10;
14972                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14973                 while (emptycount--)
14974                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14975 #endif
14976             } else if (isdigit(*p)) {
14977                 emptycount = *p++ - '0';
14978                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14979                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14980                 while (emptycount--)
14981                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14982             } else if (*p == '+' || isalpha(*p)) {
14983                 if (j >= gameInfo.boardWidth) return FALSE;
14984                 if(*p=='+') {
14985                     piece = CharToPiece(*++p);
14986                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14987                     piece = (ChessSquare) (PROMOTED piece ); p++;
14988                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14989                 } else piece = CharToPiece(*p++);
14990
14991                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14992                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14993                     piece = (ChessSquare) (PROMOTED piece);
14994                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14995                     p++;
14996                 }
14997                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14998             } else {
14999                 return FALSE;
15000             }
15001         }
15002     }
15003     while (*p == '/' || *p == ' ') p++;
15004
15005     /* [HGM] look for Crazyhouse holdings here */
15006     while(*p==' ') p++;
15007     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15008         if(*p == '[') p++;
15009         if(*p == '-' ) p++; /* empty holdings */ else {
15010             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15011             /* if we would allow FEN reading to set board size, we would   */
15012             /* have to add holdings and shift the board read so far here   */
15013             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15014                 p++;
15015                 if((int) piece >= (int) BlackPawn ) {
15016                     i = (int)piece - (int)BlackPawn;
15017                     i = PieceToNumber((ChessSquare)i);
15018                     if( i >= gameInfo.holdingsSize ) return FALSE;
15019                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15020                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15021                 } else {
15022                     i = (int)piece - (int)WhitePawn;
15023                     i = PieceToNumber((ChessSquare)i);
15024                     if( i >= gameInfo.holdingsSize ) return FALSE;
15025                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15026                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15027                 }
15028             }
15029         }
15030         if(*p == ']') p++;
15031     }
15032
15033     while(*p == ' ') p++;
15034
15035     /* Active color */
15036     c = *p++;
15037     if(appData.colorNickNames) {
15038       if( c == appData.colorNickNames[0] ) c = 'w'; else
15039       if( c == appData.colorNickNames[1] ) c = 'b';
15040     }
15041     switch (c) {
15042       case 'w':
15043         *blackPlaysFirst = FALSE;
15044         break;
15045       case 'b':
15046         *blackPlaysFirst = TRUE;
15047         break;
15048       default:
15049         return FALSE;
15050     }
15051
15052     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15053     /* return the extra info in global variiables             */
15054
15055     /* set defaults in case FEN is incomplete */
15056     board[EP_STATUS] = EP_UNKNOWN;
15057     for(i=0; i<nrCastlingRights; i++ ) {
15058         board[CASTLING][i] =
15059             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15060     }   /* assume possible unless obviously impossible */
15061     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15062     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15063     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15064                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15065     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15066     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15067     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15068                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15069     FENrulePlies = 0;
15070
15071     while(*p==' ') p++;
15072     if(nrCastlingRights) {
15073       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15074           /* castling indicator present, so default becomes no castlings */
15075           for(i=0; i<nrCastlingRights; i++ ) {
15076                  board[CASTLING][i] = NoRights;
15077           }
15078       }
15079       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15080              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15081              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15082              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15083         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15084
15085         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15086             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15087             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15088         }
15089         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15090             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15091         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15092                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15093         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15094                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15095         switch(c) {
15096           case'K':
15097               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15098               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15099               board[CASTLING][2] = whiteKingFile;
15100               break;
15101           case'Q':
15102               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15103               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15104               board[CASTLING][2] = whiteKingFile;
15105               break;
15106           case'k':
15107               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15108               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15109               board[CASTLING][5] = blackKingFile;
15110               break;
15111           case'q':
15112               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15113               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15114               board[CASTLING][5] = blackKingFile;
15115           case '-':
15116               break;
15117           default: /* FRC castlings */
15118               if(c >= 'a') { /* black rights */
15119                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15120                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15121                   if(i == BOARD_RGHT) break;
15122                   board[CASTLING][5] = i;
15123                   c -= AAA;
15124                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15125                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15126                   if(c > i)
15127                       board[CASTLING][3] = c;
15128                   else
15129                       board[CASTLING][4] = c;
15130               } else { /* white rights */
15131                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15132                     if(board[0][i] == WhiteKing) break;
15133                   if(i == BOARD_RGHT) break;
15134                   board[CASTLING][2] = i;
15135                   c -= AAA - 'a' + 'A';
15136                   if(board[0][c] >= WhiteKing) break;
15137                   if(c > i)
15138                       board[CASTLING][0] = c;
15139                   else
15140                       board[CASTLING][1] = c;
15141               }
15142         }
15143       }
15144       for(i=0; i<nrCastlingRights; i++)
15145         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15146     if (appData.debugMode) {
15147         fprintf(debugFP, "FEN castling rights:");
15148         for(i=0; i<nrCastlingRights; i++)
15149         fprintf(debugFP, " %d", board[CASTLING][i]);
15150         fprintf(debugFP, "\n");
15151     }
15152
15153       while(*p==' ') p++;
15154     }
15155
15156     /* read e.p. field in games that know e.p. capture */
15157     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15158        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15159       if(*p=='-') {
15160         p++; board[EP_STATUS] = EP_NONE;
15161       } else {
15162          char c = *p++ - AAA;
15163
15164          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15165          if(*p >= '0' && *p <='9') p++;
15166          board[EP_STATUS] = c;
15167       }
15168     }
15169
15170
15171     if(sscanf(p, "%d", &i) == 1) {
15172         FENrulePlies = i; /* 50-move ply counter */
15173         /* (The move number is still ignored)    */
15174     }
15175
15176     return TRUE;
15177 }
15178
15179 void
15180 EditPositionPasteFEN(char *fen)
15181 {
15182   if (fen != NULL) {
15183     Board initial_position;
15184
15185     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15186       DisplayError(_("Bad FEN position in clipboard"), 0);
15187       return ;
15188     } else {
15189       int savedBlackPlaysFirst = blackPlaysFirst;
15190       EditPositionEvent();
15191       blackPlaysFirst = savedBlackPlaysFirst;
15192       CopyBoard(boards[0], initial_position);
15193       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15194       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15195       DisplayBothClocks();
15196       DrawPosition(FALSE, boards[currentMove]);
15197     }
15198   }
15199 }
15200
15201 static char cseq[12] = "\\   ";
15202
15203 Boolean set_cont_sequence(char *new_seq)
15204 {
15205     int len;
15206     Boolean ret;
15207
15208     // handle bad attempts to set the sequence
15209         if (!new_seq)
15210                 return 0; // acceptable error - no debug
15211
15212     len = strlen(new_seq);
15213     ret = (len > 0) && (len < sizeof(cseq));
15214     if (ret)
15215       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15216     else if (appData.debugMode)
15217       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15218     return ret;
15219 }
15220
15221 /*
15222     reformat a source message so words don't cross the width boundary.  internal
15223     newlines are not removed.  returns the wrapped size (no null character unless
15224     included in source message).  If dest is NULL, only calculate the size required
15225     for the dest buffer.  lp argument indicats line position upon entry, and it's
15226     passed back upon exit.
15227 */
15228 int wrap(char *dest, char *src, int count, int width, int *lp)
15229 {
15230     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15231
15232     cseq_len = strlen(cseq);
15233     old_line = line = *lp;
15234     ansi = len = clen = 0;
15235
15236     for (i=0; i < count; i++)
15237     {
15238         if (src[i] == '\033')
15239             ansi = 1;
15240
15241         // if we hit the width, back up
15242         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15243         {
15244             // store i & len in case the word is too long
15245             old_i = i, old_len = len;
15246
15247             // find the end of the last word
15248             while (i && src[i] != ' ' && src[i] != '\n')
15249             {
15250                 i--;
15251                 len--;
15252             }
15253
15254             // word too long?  restore i & len before splitting it
15255             if ((old_i-i+clen) >= width)
15256             {
15257                 i = old_i;
15258                 len = old_len;
15259             }
15260
15261             // extra space?
15262             if (i && src[i-1] == ' ')
15263                 len--;
15264
15265             if (src[i] != ' ' && src[i] != '\n')
15266             {
15267                 i--;
15268                 if (len)
15269                     len--;
15270             }
15271
15272             // now append the newline and continuation sequence
15273             if (dest)
15274                 dest[len] = '\n';
15275             len++;
15276             if (dest)
15277                 strncpy(dest+len, cseq, cseq_len);
15278             len += cseq_len;
15279             line = cseq_len;
15280             clen = cseq_len;
15281             continue;
15282         }
15283
15284         if (dest)
15285             dest[len] = src[i];
15286         len++;
15287         if (!ansi)
15288             line++;
15289         if (src[i] == '\n')
15290             line = 0;
15291         if (src[i] == 'm')
15292             ansi = 0;
15293     }
15294     if (dest && appData.debugMode)
15295     {
15296         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15297             count, width, line, len, *lp);
15298         show_bytes(debugFP, src, count);
15299         fprintf(debugFP, "\ndest: ");
15300         show_bytes(debugFP, dest, len);
15301         fprintf(debugFP, "\n");
15302     }
15303     *lp = dest ? line : old_line;
15304
15305     return len;
15306 }
15307
15308 // [HGM] vari: routines for shelving variations
15309
15310 void
15311 PushTail(int firstMove, int lastMove)
15312 {
15313         int i, j, nrMoves = lastMove - firstMove;
15314
15315         if(appData.icsActive) { // only in local mode
15316                 forwardMostMove = currentMove; // mimic old ICS behavior
15317                 return;
15318         }
15319         if(storedGames >= MAX_VARIATIONS-1) return;
15320
15321         // push current tail of game on stack
15322         savedResult[storedGames] = gameInfo.result;
15323         savedDetails[storedGames] = gameInfo.resultDetails;
15324         gameInfo.resultDetails = NULL;
15325         savedFirst[storedGames] = firstMove;
15326         savedLast [storedGames] = lastMove;
15327         savedFramePtr[storedGames] = framePtr;
15328         framePtr -= nrMoves; // reserve space for the boards
15329         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15330             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15331             for(j=0; j<MOVE_LEN; j++)
15332                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15333             for(j=0; j<2*MOVE_LEN; j++)
15334                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15335             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15336             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15337             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15338             pvInfoList[firstMove+i-1].depth = 0;
15339             commentList[framePtr+i] = commentList[firstMove+i];
15340             commentList[firstMove+i] = NULL;
15341         }
15342
15343         storedGames++;
15344         forwardMostMove = firstMove; // truncate game so we can start variation
15345         if(storedGames == 1) GreyRevert(FALSE);
15346 }
15347
15348 Boolean
15349 PopTail(Boolean annotate)
15350 {
15351         int i, j, nrMoves;
15352         char buf[8000], moveBuf[20];
15353
15354         if(appData.icsActive) return FALSE; // only in local mode
15355         if(!storedGames) return FALSE; // sanity
15356         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15357
15358         storedGames--;
15359         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15360         nrMoves = savedLast[storedGames] - currentMove;
15361         if(annotate) {
15362                 int cnt = 10;
15363                 if(!WhiteOnMove(currentMove))
15364                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15365                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15366                 for(i=currentMove; i<forwardMostMove; i++) {
15367                         if(WhiteOnMove(i))
15368                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15369                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15370                         strcat(buf, moveBuf);
15371                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15372                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15373                 }
15374                 strcat(buf, ")");
15375         }
15376         for(i=1; i<=nrMoves; i++) { // copy last variation back
15377             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15378             for(j=0; j<MOVE_LEN; j++)
15379                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15380             for(j=0; j<2*MOVE_LEN; j++)
15381                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15382             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15383             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15384             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15385             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15386             commentList[currentMove+i] = commentList[framePtr+i];
15387             commentList[framePtr+i] = NULL;
15388         }
15389         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15390         framePtr = savedFramePtr[storedGames];
15391         gameInfo.result = savedResult[storedGames];
15392         if(gameInfo.resultDetails != NULL) {
15393             free(gameInfo.resultDetails);
15394       }
15395         gameInfo.resultDetails = savedDetails[storedGames];
15396         forwardMostMove = currentMove + nrMoves;
15397         if(storedGames == 0) GreyRevert(TRUE);
15398         return TRUE;
15399 }
15400
15401 void
15402 CleanupTail()
15403 {       // remove all shelved variations
15404         int i;
15405         for(i=0; i<storedGames; i++) {
15406             if(savedDetails[i])
15407                 free(savedDetails[i]);
15408             savedDetails[i] = NULL;
15409         }
15410         for(i=framePtr; i<MAX_MOVES; i++) {
15411                 if(commentList[i]) free(commentList[i]);
15412                 commentList[i] = NULL;
15413         }
15414         framePtr = MAX_MOVES-1;
15415         storedGames = 0;
15416 }
15417
15418 void
15419 LoadVariation(int index, char *text)
15420 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15421         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15422         int level = 0, move;
15423
15424         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15425         // first find outermost bracketing variation
15426         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15427             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15428                 if(*p == '{') wait = '}'; else
15429                 if(*p == '[') wait = ']'; else
15430                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15431                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15432             }
15433             if(*p == wait) wait = NULLCHAR; // closing ]} found
15434             p++;
15435         }
15436         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15437         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15438         end[1] = NULLCHAR; // clip off comment beyond variation
15439         ToNrEvent(currentMove-1);
15440         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15441         // kludge: use ParsePV() to append variation to game
15442         move = currentMove;
15443         ParsePV(start, TRUE);
15444         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15445         ClearPremoveHighlights();
15446         CommentPopDown();
15447         ToNrEvent(currentMove+1);
15448 }
15449