Fix merging bug
[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 {
315   /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
316    *
317    * usage:   safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
318    */
319
320   assert( dst != NULL );
321   assert( src != NULL );
322   assert( count > 0 );
323
324   strncpy( dst, src, count );
325   if(  dst[ count-1 ] != '\0' )
326     {
327       if(appData.debugMode)
328       printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
329     }
330   dst[ count-1 ] = '\0';
331
332   return dst;
333 }
334
335 /* Some compiler can't cast u64 to double
336  * This function do the job for us:
337
338  * We use the highest bit for cast, this only
339  * works if the highest bit is not
340  * in use (This should not happen)
341  *
342  * We used this for all compiler
343  */
344 double
345 u64ToDouble(u64 value)
346 {
347   double r;
348   u64 tmp = value & u64Const(0x7fffffffffffffff);
349   r = (double)(s64)tmp;
350   if (value & u64Const(0x8000000000000000))
351        r +=  9.2233720368547758080e18; /* 2^63 */
352  return r;
353 }
354
355 /* Fake up flags for now, as we aren't keeping track of castling
356    availability yet. [HGM] Change of logic: the flag now only
357    indicates the type of castlings allowed by the rule of the game.
358    The actual rights themselves are maintained in the array
359    castlingRights, as part of the game history, and are not probed
360    by this function.
361  */
362 int
363 PosFlags(index)
364 {
365   int flags = F_ALL_CASTLE_OK;
366   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367   switch (gameInfo.variant) {
368   case VariantSuicide:
369     flags &= ~F_ALL_CASTLE_OK;
370   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371     flags |= F_IGNORE_CHECK;
372   case VariantLosers:
373     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374     break;
375   case VariantAtomic:
376     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377     break;
378   case VariantKriegspiel:
379     flags |= F_KRIEGSPIEL_CAPTURE;
380     break;
381   case VariantCapaRandom:
382   case VariantFischeRandom:
383     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384   case VariantNoCastle:
385   case VariantShatranj:
386   case VariantCourier:
387   case VariantMakruk:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP;
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452
453 int have_sent_ICS_logon = 0;
454 int sending_ICS_login    = 0;
455 int sending_ICS_password = 0;
456
457 int movesPerSession;
458 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
459 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0;
464 TimeMark programStartTime;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 int shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void CleanupTail P((void));
503
504 ChessSquare  FIDEArray[2][BOARD_FILES] = {
505     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
506         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
508         BlackKing, BlackBishop, BlackKnight, BlackRook }
509 };
510
511 ChessSquare twoKingsArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515         BlackKing, BlackKing, BlackKnight, BlackRook }
516 };
517
518 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
520         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
521     { BlackRook, BlackMan, BlackBishop, BlackQueen,
522         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
523 };
524
525 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
526     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
527         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
528     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
529         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
530 };
531
532 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
533     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
534         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
536         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
537 };
538
539 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
540     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
541         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackMan, BlackFerz,
543         BlackKing, BlackMan, BlackKnight, BlackRook }
544 };
545
546
547 #if (BOARD_FILES>=10)
548 ChessSquare ShogiArray[2][BOARD_FILES] = {
549     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
550         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
551     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
552         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
553 };
554
555 ChessSquare XiangqiArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
557         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
559         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
560 };
561
562 ChessSquare CapablancaArray[2][BOARD_FILES] = {
563     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
564         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
566         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
567 };
568
569 ChessSquare GreatArray[2][BOARD_FILES] = {
570     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
571         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
572     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
573         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
574 };
575
576 ChessSquare JanusArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
578         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
579     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
580         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
581 };
582
583 #ifdef GOTHIC
584 ChessSquare GothicArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
586         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
588         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
589 };
590 #else // !GOTHIC
591 #define GothicArray CapablancaArray
592 #endif // !GOTHIC
593
594 #ifdef FALCON
595 ChessSquare FalconArray[2][BOARD_FILES] = {
596     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
597         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
598     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
599         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
600 };
601 #else // !FALCON
602 #define FalconArray CapablancaArray
603 #endif // !FALCON
604
605 #else // !(BOARD_FILES>=10)
606 #define XiangqiPosition FIDEArray
607 #define CapablancaArray FIDEArray
608 #define GothicArray FIDEArray
609 #define GreatArray FIDEArray
610 #endif // !(BOARD_FILES>=10)
611
612 #if (BOARD_FILES>=12)
613 ChessSquare CourierArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
615         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
617         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
618 };
619 #else // !(BOARD_FILES>=12)
620 #define CourierArray CapablancaArray
621 #endif // !(BOARD_FILES>=12)
622
623
624 Board initialPosition;
625
626
627 /* Convert str to a rating. Checks for special cases of "----",
628
629    "++++", etc. Also strips ()'s */
630 int
631 string_to_rating(str)
632   char *str;
633 {
634   while(*str && !isdigit(*str)) ++str;
635   if (!*str)
636     return 0;   /* One of the special "no rating" cases */
637   else
638     return atoi(str);
639 }
640
641 void
642 ClearProgramStats()
643 {
644     /* Init programStats */
645     programStats.movelist[0] = 0;
646     programStats.depth = 0;
647     programStats.nr_moves = 0;
648     programStats.moves_left = 0;
649     programStats.nodes = 0;
650     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
651     programStats.score = 0;
652     programStats.got_only_move = 0;
653     programStats.got_fail = 0;
654     programStats.line_is_book = 0;
655 }
656
657 void
658 InitBackEnd1()
659 {
660     int matched, min, sec;
661
662     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
663     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
664
665     GetTimeMark(&programStartTime);
666     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
667
668     ClearProgramStats();
669     programStats.ok_to_send = 1;
670     programStats.seen_stat = 0;
671
672     /*
673      * Initialize game list
674      */
675     ListNew(&gameList);
676
677
678     /*
679      * Internet chess server status
680      */
681     if (appData.icsActive) {
682         appData.matchMode = FALSE;
683         appData.matchGames = 0;
684 #if ZIPPY
685         appData.noChessProgram = !appData.zippyPlay;
686 #else
687         appData.zippyPlay = FALSE;
688         appData.zippyTalk = FALSE;
689         appData.noChessProgram = TRUE;
690 #endif
691         if (*appData.icsHelper != NULLCHAR) {
692             appData.useTelnet = TRUE;
693             appData.telnetProgram = appData.icsHelper;
694         }
695     } else {
696         appData.zippyTalk = appData.zippyPlay = FALSE;
697     }
698
699     /* [AS] Initialize pv info list [HGM] and game state */
700     {
701         int i, j;
702
703         for( i=0; i<=framePtr; i++ ) {
704             pvInfoList[i].depth = -1;
705             boards[i][EP_STATUS] = EP_NONE;
706             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
707         }
708     }
709
710     /*
711      * Parse timeControl resource
712      */
713     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
714                           appData.movesPerSession)) {
715         char buf[MSG_SIZ];
716         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
717         DisplayFatalError(buf, 0, 2);
718     }
719
720     /*
721      * Parse searchTime resource
722      */
723     if (*appData.searchTime != NULLCHAR) {
724         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
725         if (matched == 1) {
726             searchTime = min * 60;
727         } else if (matched == 2) {
728             searchTime = min * 60 + sec;
729         } else {
730             char buf[MSG_SIZ];
731             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
732             DisplayFatalError(buf, 0, 2);
733         }
734     }
735
736     /* [AS] Adjudication threshold */
737     adjudicateLossThreshold = appData.adjudicateLossThreshold;
738
739     first.which = _("first");
740     second.which = _("second");
741     first.maybeThinking = second.maybeThinking = FALSE;
742     first.pr = second.pr = NoProc;
743     first.isr = second.isr = NULL;
744     first.sendTime = second.sendTime = 2;
745     first.sendDrawOffers = 1;
746     if (appData.firstPlaysBlack) {
747         first.twoMachinesColor = "black\n";
748         second.twoMachinesColor = "white\n";
749     } else {
750         first.twoMachinesColor = "white\n";
751         second.twoMachinesColor = "black\n";
752     }
753     first.program = appData.firstChessProgram;
754     second.program = appData.secondChessProgram;
755     first.host = appData.firstHost;
756     second.host = appData.secondHost;
757     first.dir = appData.firstDirectory;
758     second.dir = appData.secondDirectory;
759     first.other = &second;
760     second.other = &first;
761     first.initString = appData.initString;
762     second.initString = appData.secondInitString;
763     first.computerString = appData.firstComputerString;
764     second.computerString = appData.secondComputerString;
765     first.useSigint = second.useSigint = TRUE;
766     first.useSigterm = second.useSigterm = TRUE;
767     first.reuse = appData.reuseFirst;
768     second.reuse = appData.reuseSecond;
769     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
770     second.nps = appData.secondNPS;
771     first.useSetboard = second.useSetboard = FALSE;
772     first.useSAN = second.useSAN = FALSE;
773     first.usePing = second.usePing = FALSE;
774     first.lastPing = second.lastPing = 0;
775     first.lastPong = second.lastPong = 0;
776     first.usePlayother = second.usePlayother = FALSE;
777     first.useColors = second.useColors = TRUE;
778     first.useUsermove = second.useUsermove = FALSE;
779     first.sendICS = second.sendICS = FALSE;
780     first.sendName = second.sendName = appData.icsActive;
781     first.sdKludge = second.sdKludge = FALSE;
782     first.stKludge = second.stKludge = FALSE;
783     TidyProgramName(first.program, first.host, first.tidy);
784     TidyProgramName(second.program, second.host, second.tidy);
785     first.matchWins = second.matchWins = 0;
786     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
787     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
788     first.analysisSupport = second.analysisSupport = 2; /* detect */
789     first.analyzing = second.analyzing = FALSE;
790     first.initDone = second.initDone = FALSE;
791
792     /* New features added by Tord: */
793     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
794     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
795     /* End of new features added by Tord. */
796     first.fenOverride  = appData.fenOverride1;
797     second.fenOverride = appData.fenOverride2;
798
799     /* [HGM] time odds: set factor for each machine */
800     first.timeOdds  = appData.firstTimeOdds;
801     second.timeOdds = appData.secondTimeOdds;
802     { float norm = 1;
803         if(appData.timeOddsMode) {
804             norm = first.timeOdds;
805             if(norm > second.timeOdds) norm = second.timeOdds;
806         }
807         first.timeOdds /= norm;
808         second.timeOdds /= norm;
809     }
810
811     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812     first.accumulateTC = appData.firstAccumulateTC;
813     second.accumulateTC = appData.secondAccumulateTC;
814     first.maxNrOfSessions = second.maxNrOfSessions = 1;
815
816     /* [HGM] debug */
817     first.debug = second.debug = FALSE;
818     first.supportsNPS = second.supportsNPS = UNKNOWN;
819
820     /* [HGM] options */
821     first.optionSettings  = appData.firstOptions;
822     second.optionSettings = appData.secondOptions;
823
824     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
825     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
826     first.isUCI = appData.firstIsUCI; /* [AS] */
827     second.isUCI = appData.secondIsUCI; /* [AS] */
828     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
829     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
830
831     if (appData.firstProtocolVersion > PROTOVER
832         || appData.firstProtocolVersion < 1)
833       {
834         char buf[MSG_SIZ];
835         int len;
836
837         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838                        appData.firstProtocolVersion);
839         if( (len > MSG_SIZ) && appData.debugMode )
840           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
841
842         DisplayFatalError(buf, 0, 2);
843       }
844     else
845       {
846         first.protocolVersion = appData.firstProtocolVersion;
847       }
848
849     if (appData.secondProtocolVersion > PROTOVER
850         || appData.secondProtocolVersion < 1)
851       {
852         char buf[MSG_SIZ];
853         int len;
854
855         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
856                        appData.secondProtocolVersion);
857         if( (len > MSG_SIZ) && appData.debugMode )
858           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
859
860         DisplayFatalError(buf, 0, 2);
861       }
862     else
863       {
864         second.protocolVersion = appData.secondProtocolVersion;
865       }
866
867     if (appData.icsActive) {
868         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
869 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
870     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
871         appData.clockMode = FALSE;
872         first.sendTime = second.sendTime = 0;
873     }
874
875 #if ZIPPY
876     /* Override some settings from environment variables, for backward
877        compatibility.  Unfortunately it's not feasible to have the env
878        vars just set defaults, at least in xboard.  Ugh.
879     */
880     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
881       ZippyInit();
882     }
883 #endif
884
885     if (appData.noChessProgram) {
886         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
887         sprintf(programVersion, "%s", PACKAGE_STRING);
888     } else {
889       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
890       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
891       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
892     }
893
894     if (!appData.icsActive) {
895       char buf[MSG_SIZ];
896       int len;
897
898       /* Check for variants that are supported only in ICS mode,
899          or not at all.  Some that are accepted here nevertheless
900          have bugs; see comments below.
901       */
902       VariantClass variant = StringToVariant(appData.variant);
903       switch (variant) {
904       case VariantBughouse:     /* need four players and two boards */
905       case VariantKriegspiel:   /* need to hide pieces and move details */
906         /* case VariantFischeRandom: (Fabien: moved below) */
907         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
908         if( (len > MSG_SIZ) && appData.debugMode )
909           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
910
911         DisplayFatalError(buf, 0, 2);
912         return;
913
914       case VariantUnknown:
915       case VariantLoadable:
916       case Variant29:
917       case Variant30:
918       case Variant31:
919       case Variant32:
920       case Variant33:
921       case Variant34:
922       case Variant35:
923       case Variant36:
924       default:
925         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
926         if( (len > MSG_SIZ) && appData.debugMode )
927           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
928
929         DisplayFatalError(buf, 0, 2);
930         return;
931
932       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
933       case VariantFairy:      /* [HGM] TestLegality definitely off! */
934       case VariantGothic:     /* [HGM] should work */
935       case VariantCapablanca: /* [HGM] should work */
936       case VariantCourier:    /* [HGM] initial forced moves not implemented */
937       case VariantShogi:      /* [HGM] could still mate with pawn drop */
938       case VariantKnightmate: /* [HGM] should work */
939       case VariantCylinder:   /* [HGM] untested */
940       case VariantFalcon:     /* [HGM] untested */
941       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
942                                  offboard interposition not understood */
943       case VariantNormal:     /* definitely works! */
944       case VariantWildCastle: /* pieces not automatically shuffled */
945       case VariantNoCastle:   /* pieces not automatically shuffled */
946       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
947       case VariantLosers:     /* should work except for win condition,
948                                  and doesn't know captures are mandatory */
949       case VariantSuicide:    /* should work except for win condition,
950                                  and doesn't know captures are mandatory */
951       case VariantGiveaway:   /* should work except for win condition,
952                                  and doesn't know captures are mandatory */
953       case VariantTwoKings:   /* should work */
954       case VariantAtomic:     /* should work except for win condition */
955       case Variant3Check:     /* should work except for win condition */
956       case VariantShatranj:   /* should work except for all win conditions */
957       case VariantMakruk:     /* should work except for daw countdown */
958       case VariantBerolina:   /* might work if TestLegality is off */
959       case VariantCapaRandom: /* should work */
960       case VariantJanus:      /* should work */
961       case VariantSuper:      /* experimental */
962       case VariantGreat:      /* experimental, requires legality testing to be off */
963         break;
964       }
965     }
966
967     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
968     InitEngineUCI( installDir, &second );
969 }
970
971 int NextIntegerFromString( char ** str, long * value )
972 {
973     int result = -1;
974     char * s = *str;
975
976     while( *s == ' ' || *s == '\t' ) {
977         s++;
978     }
979
980     *value = 0;
981
982     if( *s >= '0' && *s <= '9' ) {
983         while( *s >= '0' && *s <= '9' ) {
984             *value = *value * 10 + (*s - '0');
985             s++;
986         }
987
988         result = 0;
989     }
990
991     *str = s;
992
993     return result;
994 }
995
996 int NextTimeControlFromString( char ** str, long * value )
997 {
998     long temp;
999     int result = NextIntegerFromString( str, &temp );
1000
1001     if( result == 0 ) {
1002         *value = temp * 60; /* Minutes */
1003         if( **str == ':' ) {
1004             (*str)++;
1005             result = NextIntegerFromString( str, &temp );
1006             *value += temp; /* Seconds */
1007         }
1008     }
1009
1010     return result;
1011 }
1012
1013 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1014 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1015     int result = -1, type = 0; long temp, temp2;
1016
1017     if(**str != ':') return -1; // old params remain in force!
1018     (*str)++;
1019     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1020     if( NextIntegerFromString( str, &temp ) ) return -1;
1021     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1022
1023     if(**str != '/') {
1024         /* time only: incremental or sudden-death time control */
1025         if(**str == '+') { /* increment follows; read it */
1026             (*str)++;
1027             if(**str == '!') type = *(*str)++; // Bronstein TC
1028             if(result = NextIntegerFromString( str, &temp2)) return -1;
1029             *inc = temp2 * 1000;
1030             if(**str == '.') { // read fraction of increment
1031                 char *start = ++(*str);
1032                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1033                 temp2 *= 1000;
1034                 while(start++ < *str) temp2 /= 10;
1035                 *inc += temp2;
1036             }
1037         } else *inc = 0;
1038         *moves = 0; *tc = temp * 1000; *incType = type;
1039         return 0;
1040     }
1041
1042     (*str)++; /* classical time control */
1043     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1044
1045     if(result == 0) {
1046         *moves = temp;
1047         *tc    = temp2 * 1000;
1048         *inc   = 0;
1049         *incType = type;
1050     }
1051     return result;
1052 }
1053
1054 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1055 {   /* [HGM] get time to add from the multi-session time-control string */
1056     int incType, moves=1; /* kludge to force reading of first session */
1057     long time, increment;
1058     char *s = tcString;
1059
1060     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1061     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1062     do {
1063         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1064         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1065         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1066         if(movenr == -1) return time;    /* last move before new session     */
1067         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1068         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1069         if(!moves) return increment;     /* current session is incremental   */
1070         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1071     } while(movenr >= -1);               /* try again for next session       */
1072
1073     return 0; // no new time quota on this move
1074 }
1075
1076 int
1077 ParseTimeControl(tc, ti, mps)
1078      char *tc;
1079      float ti;
1080      int mps;
1081 {
1082   long tc1;
1083   long tc2;
1084   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1085   int min, sec=0;
1086
1087   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1088   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1089       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1090   if(ti > 0) {
1091
1092     if(mps)
1093       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1094     else
1095       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1096   } else {
1097     if(mps)
1098       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1099     else
1100       snprintf(buf, MSG_SIZ, ":%s", mytc);
1101   }
1102   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1103
1104   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1105     return FALSE;
1106   }
1107
1108   if( *tc == '/' ) {
1109     /* Parse second time control */
1110     tc++;
1111
1112     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1113       return FALSE;
1114     }
1115
1116     if( tc2 == 0 ) {
1117       return FALSE;
1118     }
1119
1120     timeControl_2 = tc2 * 1000;
1121   }
1122   else {
1123     timeControl_2 = 0;
1124   }
1125
1126   if( tc1 == 0 ) {
1127     return FALSE;
1128   }
1129
1130   timeControl = tc1 * 1000;
1131
1132   if (ti >= 0) {
1133     timeIncrement = ti * 1000;  /* convert to ms */
1134     movesPerSession = 0;
1135   } else {
1136     timeIncrement = 0;
1137     movesPerSession = mps;
1138   }
1139   return TRUE;
1140 }
1141
1142 void
1143 InitBackEnd2()
1144 {
1145     if (appData.debugMode) {
1146         fprintf(debugFP, "%s\n", programVersion);
1147     }
1148
1149     set_cont_sequence(appData.wrapContSeq);
1150     if (appData.matchGames > 0) {
1151         appData.matchMode = TRUE;
1152     } else if (appData.matchMode) {
1153         appData.matchGames = 1;
1154     }
1155     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1156         appData.matchGames = appData.sameColorGames;
1157     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1158         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1159         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1160     }
1161     Reset(TRUE, FALSE);
1162     if (appData.noChessProgram || first.protocolVersion == 1) {
1163       InitBackEnd3();
1164     } else {
1165       /* kludge: allow timeout for initial "feature" commands */
1166       FreezeUI();
1167       DisplayMessage("", _("Starting chess program"));
1168       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1169     }
1170 }
1171
1172 void
1173 InitBackEnd3 P((void))
1174 {
1175     GameMode initialMode;
1176     char buf[MSG_SIZ];
1177     int err, len;
1178
1179     InitChessProgram(&first, startedFromSetupPosition);
1180
1181     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1182         free(programVersion);
1183         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1184         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1185     }
1186
1187     if (appData.icsActive) {
1188 #ifdef WIN32
1189         /* [DM] Make a console window if needed [HGM] merged ifs */
1190         ConsoleCreate();
1191 #endif
1192         err = establish();
1193         if (err != 0)
1194           {
1195             if (*appData.icsCommPort != NULLCHAR)
1196               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1197                              appData.icsCommPort);
1198             else
1199               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1200                         appData.icsHost, appData.icsPort);
1201
1202             if( (len > MSG_SIZ) && appData.debugMode )
1203               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1204
1205             DisplayFatalError(buf, err, 1);
1206             return;
1207         }
1208         SetICSMode();
1209         telnetISR =
1210           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1211         fromUserISR =
1212           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1213         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1214             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1215     } else if (appData.noChessProgram) {
1216         SetNCPMode();
1217     } else {
1218         SetGNUMode();
1219     }
1220
1221     if (*appData.cmailGameName != NULLCHAR) {
1222         SetCmailMode();
1223         OpenLoopback(&cmailPR);
1224         cmailISR =
1225           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1226     }
1227
1228     ThawUI();
1229     DisplayMessage("", "");
1230     if (StrCaseCmp(appData.initialMode, "") == 0) {
1231       initialMode = BeginningOfGame;
1232     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1233       initialMode = TwoMachinesPlay;
1234     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1235       initialMode = AnalyzeFile;
1236     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1237       initialMode = AnalyzeMode;
1238     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1239       initialMode = MachinePlaysWhite;
1240     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1241       initialMode = MachinePlaysBlack;
1242     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1243       initialMode = EditGame;
1244     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1245       initialMode = EditPosition;
1246     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1247       initialMode = Training;
1248     } else {
1249       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1250       if( (len > MSG_SIZ) && appData.debugMode )
1251         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1252
1253       DisplayFatalError(buf, 0, 2);
1254       return;
1255     }
1256
1257     if (appData.matchMode) {
1258         /* Set up machine vs. machine match */
1259         if (appData.noChessProgram) {
1260             DisplayFatalError(_("Can't have a match with no chess programs"),
1261                               0, 2);
1262             return;
1263         }
1264         matchMode = TRUE;
1265         matchGame = 1;
1266         if (*appData.loadGameFile != NULLCHAR) {
1267             int index = appData.loadGameIndex; // [HGM] autoinc
1268             if(index<0) lastIndex = index = 1;
1269             if (!LoadGameFromFile(appData.loadGameFile,
1270                                   index,
1271                                   appData.loadGameFile, FALSE)) {
1272                 DisplayFatalError(_("Bad game file"), 0, 1);
1273                 return;
1274             }
1275         } else if (*appData.loadPositionFile != NULLCHAR) {
1276             int index = appData.loadPositionIndex; // [HGM] autoinc
1277             if(index<0) lastIndex = index = 1;
1278             if (!LoadPositionFromFile(appData.loadPositionFile,
1279                                       index,
1280                                       appData.loadPositionFile)) {
1281                 DisplayFatalError(_("Bad position file"), 0, 1);
1282                 return;
1283             }
1284         }
1285         TwoMachinesEvent();
1286     } else if (*appData.cmailGameName != NULLCHAR) {
1287         /* Set up cmail mode */
1288         ReloadCmailMsgEvent(TRUE);
1289     } else {
1290         /* Set up other modes */
1291         if (initialMode == AnalyzeFile) {
1292           if (*appData.loadGameFile == NULLCHAR) {
1293             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1294             return;
1295           }
1296         }
1297         if (*appData.loadGameFile != NULLCHAR) {
1298             (void) LoadGameFromFile(appData.loadGameFile,
1299                                     appData.loadGameIndex,
1300                                     appData.loadGameFile, TRUE);
1301         } else if (*appData.loadPositionFile != NULLCHAR) {
1302             (void) LoadPositionFromFile(appData.loadPositionFile,
1303                                         appData.loadPositionIndex,
1304                                         appData.loadPositionFile);
1305             /* [HGM] try to make self-starting even after FEN load */
1306             /* to allow automatic setup of fairy variants with wtm */
1307             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1308                 gameMode = BeginningOfGame;
1309                 setboardSpoiledMachineBlack = 1;
1310             }
1311             /* [HGM] loadPos: make that every new game uses the setup */
1312             /* from file as long as we do not switch variant          */
1313             if(!blackPlaysFirst) {
1314                 startedFromPositionFile = TRUE;
1315                 CopyBoard(filePosition, boards[0]);
1316             }
1317         }
1318         if (initialMode == AnalyzeMode) {
1319           if (appData.noChessProgram) {
1320             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1321             return;
1322           }
1323           if (appData.icsActive) {
1324             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1325             return;
1326           }
1327           AnalyzeModeEvent();
1328         } else if (initialMode == AnalyzeFile) {
1329           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1330           ShowThinkingEvent();
1331           AnalyzeFileEvent();
1332           AnalysisPeriodicEvent(1);
1333         } else if (initialMode == MachinePlaysWhite) {
1334           if (appData.noChessProgram) {
1335             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1336                               0, 2);
1337             return;
1338           }
1339           if (appData.icsActive) {
1340             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1341                               0, 2);
1342             return;
1343           }
1344           MachineWhiteEvent();
1345         } else if (initialMode == MachinePlaysBlack) {
1346           if (appData.noChessProgram) {
1347             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1348                               0, 2);
1349             return;
1350           }
1351           if (appData.icsActive) {
1352             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1353                               0, 2);
1354             return;
1355           }
1356           MachineBlackEvent();
1357         } else if (initialMode == TwoMachinesPlay) {
1358           if (appData.noChessProgram) {
1359             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1360                               0, 2);
1361             return;
1362           }
1363           if (appData.icsActive) {
1364             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1365                               0, 2);
1366             return;
1367           }
1368           TwoMachinesEvent();
1369         } else if (initialMode == EditGame) {
1370           EditGameEvent();
1371         } else if (initialMode == EditPosition) {
1372           EditPositionEvent();
1373         } else if (initialMode == Training) {
1374           if (*appData.loadGameFile == NULLCHAR) {
1375             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1376             return;
1377           }
1378           TrainingEvent();
1379         }
1380     }
1381 }
1382
1383 /*
1384  * Establish will establish a contact to a remote host.port.
1385  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1386  *  used to talk to the host.
1387  * Returns 0 if okay, error code if not.
1388  */
1389 int
1390 establish()
1391 {
1392     char buf[MSG_SIZ];
1393
1394     if (*appData.icsCommPort != NULLCHAR) {
1395         /* Talk to the host through a serial comm port */
1396         return OpenCommPort(appData.icsCommPort, &icsPR);
1397
1398     } else if (*appData.gateway != NULLCHAR) {
1399         if (*appData.remoteShell == NULLCHAR) {
1400             /* Use the rcmd protocol to run telnet program on a gateway host */
1401             snprintf(buf, sizeof(buf), "%s %s %s",
1402                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1403             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1404
1405         } else {
1406             /* Use the rsh program to run telnet program on a gateway host */
1407             if (*appData.remoteUser == NULLCHAR) {
1408                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1409                         appData.gateway, appData.telnetProgram,
1410                         appData.icsHost, appData.icsPort);
1411             } else {
1412                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1413                         appData.remoteShell, appData.gateway,
1414                         appData.remoteUser, appData.telnetProgram,
1415                         appData.icsHost, appData.icsPort);
1416             }
1417             return StartChildProcess(buf, "", &icsPR);
1418
1419         }
1420     } else if (appData.useTelnet) {
1421         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1422
1423     } else {
1424         /* TCP socket interface differs somewhat between
1425            Unix and NT; handle details in the front end.
1426            */
1427         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1428     }
1429 }
1430
1431 void EscapeExpand(char *p, char *q)
1432 {       // [HGM] initstring: routine to shape up string arguments
1433         while(*p++ = *q++) if(p[-1] == '\\')
1434             switch(*q++) {
1435                 case 'n': p[-1] = '\n'; break;
1436                 case 'r': p[-1] = '\r'; break;
1437                 case 't': p[-1] = '\t'; break;
1438                 case '\\': p[-1] = '\\'; break;
1439                 case 0: *p = 0; return;
1440                 default: p[-1] = q[-1]; break;
1441             }
1442 }
1443
1444 void
1445 show_bytes(fp, buf, count)
1446      FILE *fp;
1447      char *buf;
1448      int count;
1449 {
1450     while (count--) {
1451         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1452             fprintf(fp, "\\%03o", *buf & 0xff);
1453         } else {
1454             putc(*buf, fp);
1455         }
1456         buf++;
1457     }
1458     fflush(fp);
1459 }
1460
1461 /* Returns an errno value */
1462 int
1463 OutputMaybeTelnet(pr, message, count, outError)
1464      ProcRef pr;
1465      char *message;
1466      int count;
1467      int *outError;
1468 {
1469     char buf[8192], *p, *q, *buflim;
1470     int left, newcount, outcount;
1471
1472     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1473         *appData.gateway != NULLCHAR) {
1474         if (appData.debugMode) {
1475             fprintf(debugFP, ">ICS: ");
1476             show_bytes(debugFP, message, count);
1477             fprintf(debugFP, "\n");
1478         }
1479         return OutputToProcess(pr, message, count, outError);
1480     }
1481
1482     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1483     p = message;
1484     q = buf;
1485     left = count;
1486     newcount = 0;
1487     while (left) {
1488         if (q >= buflim) {
1489             if (appData.debugMode) {
1490                 fprintf(debugFP, ">ICS: ");
1491                 show_bytes(debugFP, buf, newcount);
1492                 fprintf(debugFP, "\n");
1493             }
1494             outcount = OutputToProcess(pr, buf, newcount, outError);
1495             if (outcount < newcount) return -1; /* to be sure */
1496             q = buf;
1497             newcount = 0;
1498         }
1499         if (*p == '\n') {
1500             *q++ = '\r';
1501             newcount++;
1502         } else if (((unsigned char) *p) == TN_IAC) {
1503             *q++ = (char) TN_IAC;
1504             newcount ++;
1505         }
1506         *q++ = *p++;
1507         newcount++;
1508         left--;
1509     }
1510     if (appData.debugMode) {
1511         fprintf(debugFP, ">ICS: ");
1512         show_bytes(debugFP, buf, newcount);
1513         fprintf(debugFP, "\n");
1514     }
1515     outcount = OutputToProcess(pr, buf, newcount, outError);
1516     if (outcount < newcount) return -1; /* to be sure */
1517     return count;
1518 }
1519
1520 void
1521 read_from_player(isr, closure, message, count, error)
1522      InputSourceRef isr;
1523      VOIDSTAR closure;
1524      char *message;
1525      int count;
1526      int error;
1527 {
1528     int outError, outCount;
1529     static int gotEof = 0;
1530
1531     /* Pass data read from player on to ICS */
1532     if (count > 0) {
1533         gotEof = 0;
1534         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1535         if (outCount < count) {
1536             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1537         }
1538     } else if (count < 0) {
1539         RemoveInputSource(isr);
1540         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1541     } else if (gotEof++ > 0) {
1542         RemoveInputSource(isr);
1543         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1544     }
1545 }
1546
1547 void
1548 KeepAlive()
1549 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1550     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1551     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1552     SendToICS("date\n");
1553     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1554 }
1555
1556 /* added routine for printf style output to ics */
1557 void ics_printf(char *format, ...)
1558 {
1559     char buffer[MSG_SIZ];
1560     va_list args;
1561
1562     va_start(args, format);
1563     vsnprintf(buffer, sizeof(buffer), format, args);
1564     buffer[sizeof(buffer)-1] = '\0';
1565     SendToICS(buffer);
1566     va_end(args);
1567 }
1568
1569 void
1570 SendToICS(s)
1571      char *s;
1572 {
1573     int count, outCount, outError;
1574
1575     if (icsPR == NULL) return;
1576
1577     count = strlen(s);
1578     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1579     if (outCount < count) {
1580         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1581     }
1582 }
1583
1584 /* This is used for sending logon scripts to the ICS. Sending
1585    without a delay causes problems when using timestamp on ICC
1586    (at least on my machine). */
1587 void
1588 SendToICSDelayed(s,msdelay)
1589      char *s;
1590      long msdelay;
1591 {
1592     int count, outCount, outError;
1593
1594     if (icsPR == NULL) return;
1595
1596     count = strlen(s);
1597     if (appData.debugMode) {
1598         fprintf(debugFP, ">ICS: ");
1599         show_bytes(debugFP, s, count);
1600         fprintf(debugFP, "\n");
1601     }
1602     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1603                                       msdelay);
1604     if (outCount < count) {
1605         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1606     }
1607 }
1608
1609
1610 /* Remove all highlighting escape sequences in s
1611    Also deletes any suffix starting with '('
1612    */
1613 char *
1614 StripHighlightAndTitle(s)
1615      char *s;
1616 {
1617     static char retbuf[MSG_SIZ];
1618     char *p = retbuf;
1619
1620     while (*s != NULLCHAR) {
1621         while (*s == '\033') {
1622             while (*s != NULLCHAR && !isalpha(*s)) s++;
1623             if (*s != NULLCHAR) s++;
1624         }
1625         while (*s != NULLCHAR && *s != '\033') {
1626             if (*s == '(' || *s == '[') {
1627                 *p = NULLCHAR;
1628                 return retbuf;
1629             }
1630             *p++ = *s++;
1631         }
1632     }
1633     *p = NULLCHAR;
1634     return retbuf;
1635 }
1636
1637 /* Remove all highlighting escape sequences in s */
1638 char *
1639 StripHighlight(s)
1640      char *s;
1641 {
1642     static char retbuf[MSG_SIZ];
1643     char *p = retbuf;
1644
1645     while (*s != NULLCHAR) {
1646         while (*s == '\033') {
1647             while (*s != NULLCHAR && !isalpha(*s)) s++;
1648             if (*s != NULLCHAR) s++;
1649         }
1650         while (*s != NULLCHAR && *s != '\033') {
1651             *p++ = *s++;
1652         }
1653     }
1654     *p = NULLCHAR;
1655     return retbuf;
1656 }
1657
1658 char *variantNames[] = VARIANT_NAMES;
1659 char *
1660 VariantName(v)
1661      VariantClass v;
1662 {
1663     return variantNames[v];
1664 }
1665
1666
1667 /* Identify a variant from the strings the chess servers use or the
1668    PGN Variant tag names we use. */
1669 VariantClass
1670 StringToVariant(e)
1671      char *e;
1672 {
1673     char *p;
1674     int wnum = -1;
1675     VariantClass v = VariantNormal;
1676     int i, found = FALSE;
1677     char buf[MSG_SIZ];
1678     int len;
1679
1680     if (!e) return v;
1681
1682     /* [HGM] skip over optional board-size prefixes */
1683     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1684         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1685         while( *e++ != '_');
1686     }
1687
1688     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1689         v = VariantNormal;
1690         found = TRUE;
1691     } else
1692     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1693       if (StrCaseStr(e, variantNames[i])) {
1694         v = (VariantClass) i;
1695         found = TRUE;
1696         break;
1697       }
1698     }
1699
1700     if (!found) {
1701       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1702           || StrCaseStr(e, "wild/fr")
1703           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1704         v = VariantFischeRandom;
1705       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1706                  (i = 1, p = StrCaseStr(e, "w"))) {
1707         p += i;
1708         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1709         if (isdigit(*p)) {
1710           wnum = atoi(p);
1711         } else {
1712           wnum = -1;
1713         }
1714         switch (wnum) {
1715         case 0: /* FICS only, actually */
1716         case 1:
1717           /* Castling legal even if K starts on d-file */
1718           v = VariantWildCastle;
1719           break;
1720         case 2:
1721         case 3:
1722         case 4:
1723           /* Castling illegal even if K & R happen to start in
1724              normal positions. */
1725           v = VariantNoCastle;
1726           break;
1727         case 5:
1728         case 7:
1729         case 8:
1730         case 10:
1731         case 11:
1732         case 12:
1733         case 13:
1734         case 14:
1735         case 15:
1736         case 18:
1737         case 19:
1738           /* Castling legal iff K & R start in normal positions */
1739           v = VariantNormal;
1740           break;
1741         case 6:
1742         case 20:
1743         case 21:
1744           /* Special wilds for position setup; unclear what to do here */
1745           v = VariantLoadable;
1746           break;
1747         case 9:
1748           /* Bizarre ICC game */
1749           v = VariantTwoKings;
1750           break;
1751         case 16:
1752           v = VariantKriegspiel;
1753           break;
1754         case 17:
1755           v = VariantLosers;
1756           break;
1757         case 22:
1758           v = VariantFischeRandom;
1759           break;
1760         case 23:
1761           v = VariantCrazyhouse;
1762           break;
1763         case 24:
1764           v = VariantBughouse;
1765           break;
1766         case 25:
1767           v = Variant3Check;
1768           break;
1769         case 26:
1770           /* Not quite the same as FICS suicide! */
1771           v = VariantGiveaway;
1772           break;
1773         case 27:
1774           v = VariantAtomic;
1775           break;
1776         case 28:
1777           v = VariantShatranj;
1778           break;
1779
1780         /* Temporary names for future ICC types.  The name *will* change in
1781            the next xboard/WinBoard release after ICC defines it. */
1782         case 29:
1783           v = Variant29;
1784           break;
1785         case 30:
1786           v = Variant30;
1787           break;
1788         case 31:
1789           v = Variant31;
1790           break;
1791         case 32:
1792           v = Variant32;
1793           break;
1794         case 33:
1795           v = Variant33;
1796           break;
1797         case 34:
1798           v = Variant34;
1799           break;
1800         case 35:
1801           v = Variant35;
1802           break;
1803         case 36:
1804           v = Variant36;
1805           break;
1806         case 37:
1807           v = VariantShogi;
1808           break;
1809         case 38:
1810           v = VariantXiangqi;
1811           break;
1812         case 39:
1813           v = VariantCourier;
1814           break;
1815         case 40:
1816           v = VariantGothic;
1817           break;
1818         case 41:
1819           v = VariantCapablanca;
1820           break;
1821         case 42:
1822           v = VariantKnightmate;
1823           break;
1824         case 43:
1825           v = VariantFairy;
1826           break;
1827         case 44:
1828           v = VariantCylinder;
1829           break;
1830         case 45:
1831           v = VariantFalcon;
1832           break;
1833         case 46:
1834           v = VariantCapaRandom;
1835           break;
1836         case 47:
1837           v = VariantBerolina;
1838           break;
1839         case 48:
1840           v = VariantJanus;
1841           break;
1842         case 49:
1843           v = VariantSuper;
1844           break;
1845         case 50:
1846           v = VariantGreat;
1847           break;
1848         case -1:
1849           /* Found "wild" or "w" in the string but no number;
1850              must assume it's normal chess. */
1851           v = VariantNormal;
1852           break;
1853         default:
1854           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1855           if( (len > MSG_SIZ) && appData.debugMode )
1856             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1857
1858           DisplayError(buf, 0);
1859           v = VariantUnknown;
1860           break;
1861         }
1862       }
1863     }
1864     if (appData.debugMode) {
1865       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1866               e, wnum, VariantName(v));
1867     }
1868     return v;
1869 }
1870
1871 static int leftover_start = 0, leftover_len = 0;
1872 char star_match[STAR_MATCH_N][MSG_SIZ];
1873
1874 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1875    advance *index beyond it, and set leftover_start to the new value of
1876    *index; else return FALSE.  If pattern contains the character '*', it
1877    matches any sequence of characters not containing '\r', '\n', or the
1878    character following the '*' (if any), and the matched sequence(s) are
1879    copied into star_match.
1880    */
1881 int
1882 looking_at(buf, index, pattern)
1883      char *buf;
1884      int *index;
1885      char *pattern;
1886 {
1887     char *bufp = &buf[*index], *patternp = pattern;
1888     int star_count = 0;
1889     char *matchp = star_match[0];
1890
1891     for (;;) {
1892         if (*patternp == NULLCHAR) {
1893             *index = leftover_start = bufp - buf;
1894             *matchp = NULLCHAR;
1895             return TRUE;
1896         }
1897         if (*bufp == NULLCHAR) return FALSE;
1898         if (*patternp == '*') {
1899             if (*bufp == *(patternp + 1)) {
1900                 *matchp = NULLCHAR;
1901                 matchp = star_match[++star_count];
1902                 patternp += 2;
1903                 bufp++;
1904                 continue;
1905             } else if (*bufp == '\n' || *bufp == '\r') {
1906                 patternp++;
1907                 if (*patternp == NULLCHAR)
1908                   continue;
1909                 else
1910                   return FALSE;
1911             } else {
1912                 *matchp++ = *bufp++;
1913                 continue;
1914             }
1915         }
1916         if (*patternp != *bufp) return FALSE;
1917         patternp++;
1918         bufp++;
1919     }
1920 }
1921
1922 void
1923 SendToPlayer(data, length)
1924      char *data;
1925      int length;
1926 {
1927     int error, outCount;
1928     outCount = OutputToProcess(NoProc, data, length, &error);
1929     if (outCount < length) {
1930         DisplayFatalError(_("Error writing to display"), error, 1);
1931     }
1932 }
1933
1934 void
1935 PackHolding(packed, holding)
1936      char packed[];
1937      char *holding;
1938 {
1939     char *p = holding;
1940     char *q = packed;
1941     int runlength = 0;
1942     int curr = 9999;
1943     do {
1944         if (*p == curr) {
1945             runlength++;
1946         } else {
1947             switch (runlength) {
1948               case 0:
1949                 break;
1950               case 1:
1951                 *q++ = curr;
1952                 break;
1953               case 2:
1954                 *q++ = curr;
1955                 *q++ = curr;
1956                 break;
1957               default:
1958                 sprintf(q, "%d", runlength);
1959                 while (*q) q++;
1960                 *q++ = curr;
1961                 break;
1962             }
1963             runlength = 1;
1964             curr = *p;
1965         }
1966     } while (*p++);
1967     *q = NULLCHAR;
1968 }
1969
1970 /* Telnet protocol requests from the front end */
1971 void
1972 TelnetRequest(ddww, option)
1973      unsigned char ddww, option;
1974 {
1975     unsigned char msg[3];
1976     int outCount, outError;
1977
1978     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1979
1980     if (appData.debugMode) {
1981         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1982         switch (ddww) {
1983           case TN_DO:
1984             ddwwStr = "DO";
1985             break;
1986           case TN_DONT:
1987             ddwwStr = "DONT";
1988             break;
1989           case TN_WILL:
1990             ddwwStr = "WILL";
1991             break;
1992           case TN_WONT:
1993             ddwwStr = "WONT";
1994             break;
1995           default:
1996             ddwwStr = buf1;
1997             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1998             break;
1999         }
2000         switch (option) {
2001           case TN_ECHO:
2002             optionStr = "ECHO";
2003             break;
2004           default:
2005             optionStr = buf2;
2006             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2007             break;
2008         }
2009         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2010     }
2011     msg[0] = TN_IAC;
2012     msg[1] = ddww;
2013     msg[2] = option;
2014     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2015     if (outCount < 3) {
2016         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2017     }
2018 }
2019
2020 void
2021 DoEcho()
2022 {
2023     if (!appData.icsActive) return;
2024     TelnetRequest(TN_DO, TN_ECHO);
2025 }
2026
2027 void
2028 DontEcho()
2029 {
2030     if (!appData.icsActive) return;
2031     TelnetRequest(TN_DONT, TN_ECHO);
2032 }
2033
2034 void
2035 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2036 {
2037     /* put the holdings sent to us by the server on the board holdings area */
2038     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2039     char p;
2040     ChessSquare piece;
2041
2042     if(gameInfo.holdingsWidth < 2)  return;
2043     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2044         return; // prevent overwriting by pre-board holdings
2045
2046     if( (int)lowestPiece >= BlackPawn ) {
2047         holdingsColumn = 0;
2048         countsColumn = 1;
2049         holdingsStartRow = BOARD_HEIGHT-1;
2050         direction = -1;
2051     } else {
2052         holdingsColumn = BOARD_WIDTH-1;
2053         countsColumn = BOARD_WIDTH-2;
2054         holdingsStartRow = 0;
2055         direction = 1;
2056     }
2057
2058     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2059         board[i][holdingsColumn] = EmptySquare;
2060         board[i][countsColumn]   = (ChessSquare) 0;
2061     }
2062     while( (p=*holdings++) != NULLCHAR ) {
2063         piece = CharToPiece( ToUpper(p) );
2064         if(piece == EmptySquare) continue;
2065         /*j = (int) piece - (int) WhitePawn;*/
2066         j = PieceToNumber(piece);
2067         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2068         if(j < 0) continue;               /* should not happen */
2069         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2070         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2071         board[holdingsStartRow+j*direction][countsColumn]++;
2072     }
2073 }
2074
2075
2076 void
2077 VariantSwitch(Board board, VariantClass newVariant)
2078 {
2079    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2080    static Board oldBoard;
2081
2082    startedFromPositionFile = FALSE;
2083    if(gameInfo.variant == newVariant) return;
2084
2085    /* [HGM] This routine is called each time an assignment is made to
2086     * gameInfo.variant during a game, to make sure the board sizes
2087     * are set to match the new variant. If that means adding or deleting
2088     * holdings, we shift the playing board accordingly
2089     * This kludge is needed because in ICS observe mode, we get boards
2090     * of an ongoing game without knowing the variant, and learn about the
2091     * latter only later. This can be because of the move list we requested,
2092     * in which case the game history is refilled from the beginning anyway,
2093     * but also when receiving holdings of a crazyhouse game. In the latter
2094     * case we want to add those holdings to the already received position.
2095     */
2096
2097
2098    if (appData.debugMode) {
2099      fprintf(debugFP, "Switch board from %s to %s\n",
2100              VariantName(gameInfo.variant), VariantName(newVariant));
2101      setbuf(debugFP, NULL);
2102    }
2103    shuffleOpenings = 0;       /* [HGM] shuffle */
2104    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2105    switch(newVariant)
2106      {
2107      case VariantShogi:
2108        newWidth = 9;  newHeight = 9;
2109        gameInfo.holdingsSize = 7;
2110      case VariantBughouse:
2111      case VariantCrazyhouse:
2112        newHoldingsWidth = 2; break;
2113      case VariantGreat:
2114        newWidth = 10;
2115      case VariantSuper:
2116        newHoldingsWidth = 2;
2117        gameInfo.holdingsSize = 8;
2118        break;
2119      case VariantGothic:
2120      case VariantCapablanca:
2121      case VariantCapaRandom:
2122        newWidth = 10;
2123      default:
2124        newHoldingsWidth = gameInfo.holdingsSize = 0;
2125      };
2126
2127    if(newWidth  != gameInfo.boardWidth  ||
2128       newHeight != gameInfo.boardHeight ||
2129       newHoldingsWidth != gameInfo.holdingsWidth ) {
2130
2131      /* shift position to new playing area, if needed */
2132      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2133        for(i=0; i<BOARD_HEIGHT; i++)
2134          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2135            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2136              board[i][j];
2137        for(i=0; i<newHeight; i++) {
2138          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2139          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2140        }
2141      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2142        for(i=0; i<BOARD_HEIGHT; i++)
2143          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2144            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2145              board[i][j];
2146      }
2147      gameInfo.boardWidth  = newWidth;
2148      gameInfo.boardHeight = newHeight;
2149      gameInfo.holdingsWidth = newHoldingsWidth;
2150      gameInfo.variant = newVariant;
2151      InitDrawingSizes(-2, 0);
2152    } else gameInfo.variant = newVariant;
2153    CopyBoard(oldBoard, board);   // remember correctly formatted board
2154      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2155    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2156 }
2157
2158 static int loggedOn = FALSE;
2159
2160 /*-- Game start info cache: --*/
2161 int gs_gamenum;
2162 char gs_kind[MSG_SIZ];
2163 static char player1Name[128] = "";
2164 static char player2Name[128] = "";
2165 static char cont_seq[] = "\n\\   ";
2166 static int player1Rating = -1;
2167 static int player2Rating = -1;
2168 /*----------------------------*/
2169
2170 ColorClass curColor = ColorNormal;
2171 int suppressKibitz = 0;
2172
2173 // [HGM] seekgraph
2174 Boolean soughtPending = FALSE;
2175 Boolean seekGraphUp;
2176 #define MAX_SEEK_ADS 200
2177 #define SQUARE 0x80
2178 char *seekAdList[MAX_SEEK_ADS];
2179 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2180 float tcList[MAX_SEEK_ADS];
2181 char colorList[MAX_SEEK_ADS];
2182 int nrOfSeekAds = 0;
2183 int minRating = 1010, maxRating = 2800;
2184 int hMargin = 10, vMargin = 20, h, w;
2185 extern int squareSize, lineGap;
2186
2187 void
2188 PlotSeekAd(int i)
2189 {
2190         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2191         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2192         if(r < minRating+100 && r >=0 ) r = minRating+100;
2193         if(r > maxRating) r = maxRating;
2194         if(tc < 1.) tc = 1.;
2195         if(tc > 95.) tc = 95.;
2196         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2197         y = ((double)r - minRating)/(maxRating - minRating)
2198             * (h-vMargin-squareSize/8-1) + vMargin;
2199         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2200         if(strstr(seekAdList[i], " u ")) color = 1;
2201         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2202            !strstr(seekAdList[i], "bullet") &&
2203            !strstr(seekAdList[i], "blitz") &&
2204            !strstr(seekAdList[i], "standard") ) color = 2;
2205         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2206         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2207 }
2208
2209 void
2210 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2211 {
2212         char buf[MSG_SIZ], *ext = "";
2213         VariantClass v = StringToVariant(type);
2214         if(strstr(type, "wild")) {
2215             ext = type + 4; // append wild number
2216             if(v == VariantFischeRandom) type = "chess960"; else
2217             if(v == VariantLoadable) type = "setup"; else
2218             type = VariantName(v);
2219         }
2220         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2221         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2222             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2223             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2224             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2225             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2226             seekNrList[nrOfSeekAds] = nr;
2227             zList[nrOfSeekAds] = 0;
2228             seekAdList[nrOfSeekAds++] = StrSave(buf);
2229             if(plot) PlotSeekAd(nrOfSeekAds-1);
2230         }
2231 }
2232
2233 void
2234 EraseSeekDot(int i)
2235 {
2236     int x = xList[i], y = yList[i], d=squareSize/4, k;
2237     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2238     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2239     // now replot every dot that overlapped
2240     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2241         int xx = xList[k], yy = yList[k];
2242         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2243             DrawSeekDot(xx, yy, colorList[k]);
2244     }
2245 }
2246
2247 void
2248 RemoveSeekAd(int nr)
2249 {
2250         int i;
2251         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2252             EraseSeekDot(i);
2253             if(seekAdList[i]) free(seekAdList[i]);
2254             seekAdList[i] = seekAdList[--nrOfSeekAds];
2255             seekNrList[i] = seekNrList[nrOfSeekAds];
2256             ratingList[i] = ratingList[nrOfSeekAds];
2257             colorList[i]  = colorList[nrOfSeekAds];
2258             tcList[i] = tcList[nrOfSeekAds];
2259             xList[i]  = xList[nrOfSeekAds];
2260             yList[i]  = yList[nrOfSeekAds];
2261             zList[i]  = zList[nrOfSeekAds];
2262             seekAdList[nrOfSeekAds] = NULL;
2263             break;
2264         }
2265 }
2266
2267 Boolean
2268 MatchSoughtLine(char *line)
2269 {
2270     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2271     int nr, base, inc, u=0; char dummy;
2272
2273     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2274        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2275        (u=1) &&
2276        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2277         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2278         // match: compact and save the line
2279         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2280         return TRUE;
2281     }
2282     return FALSE;
2283 }
2284
2285 int
2286 DrawSeekGraph()
2287 {
2288     int i;
2289     if(!seekGraphUp) return FALSE;
2290     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2291     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2292
2293     DrawSeekBackground(0, 0, w, h);
2294     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2295     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2296     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2297         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2298         yy = h-1-yy;
2299         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2300         if(i%500 == 0) {
2301             char buf[MSG_SIZ];
2302             snprintf(buf, MSG_SIZ, "%d", i);
2303             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2304         }
2305     }
2306     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2307     for(i=1; i<100; i+=(i<10?1:5)) {
2308         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2309         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2310         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2311             char buf[MSG_SIZ];
2312             snprintf(buf, MSG_SIZ, "%d", i);
2313             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2314         }
2315     }
2316     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2317     return TRUE;
2318 }
2319
2320 int SeekGraphClick(ClickType click, int x, int y, int moving)
2321 {
2322     static int lastDown = 0, displayed = 0, lastSecond;
2323     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2324         if(click == Release || moving) return FALSE;
2325         nrOfSeekAds = 0;
2326         soughtPending = TRUE;
2327         SendToICS(ics_prefix);
2328         SendToICS("sought\n"); // should this be "sought all"?
2329     } else { // issue challenge based on clicked ad
2330         int dist = 10000; int i, closest = 0, second = 0;
2331         for(i=0; i<nrOfSeekAds; i++) {
2332             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2333             if(d < dist) { dist = d; closest = i; }
2334             second += (d - zList[i] < 120); // count in-range ads
2335             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2336         }
2337         if(dist < 120) {
2338             char buf[MSG_SIZ];
2339             second = (second > 1);
2340             if(displayed != closest || second != lastSecond) {
2341                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2342                 lastSecond = second; displayed = closest;
2343             }
2344             if(click == Press) {
2345                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2346                 lastDown = closest;
2347                 return TRUE;
2348             } // on press 'hit', only show info
2349             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2350             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2351             SendToICS(ics_prefix);
2352             SendToICS(buf);
2353             return TRUE; // let incoming board of started game pop down the graph
2354         } else if(click == Release) { // release 'miss' is ignored
2355             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2356             if(moving == 2) { // right up-click
2357                 nrOfSeekAds = 0; // refresh graph
2358                 soughtPending = TRUE;
2359                 SendToICS(ics_prefix);
2360                 SendToICS("sought\n"); // should this be "sought all"?
2361             }
2362             return TRUE;
2363         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2364         // press miss or release hit 'pop down' seek graph
2365         seekGraphUp = FALSE;
2366         DrawPosition(TRUE, NULL);
2367     }
2368     return TRUE;
2369 }
2370
2371 void
2372 read_from_ics(isr, closure, data, count, error)
2373      InputSourceRef isr;
2374      VOIDSTAR closure;
2375      char *data;
2376      int count;
2377      int error;
2378 {
2379 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2380 #define STARTED_NONE 0
2381 #define STARTED_MOVES 1
2382 #define STARTED_BOARD 2
2383 #define STARTED_OBSERVE 3
2384 #define STARTED_HOLDINGS 4
2385 #define STARTED_CHATTER 5
2386 #define STARTED_COMMENT 6
2387 #define STARTED_MOVES_NOHIDE 7
2388
2389     static int started = STARTED_NONE;
2390     static char parse[20000];
2391     static int parse_pos = 0;
2392     static char buf[BUF_SIZE + 1];
2393     static int firstTime = TRUE, intfSet = FALSE;
2394     static ColorClass prevColor = ColorNormal;
2395     static int savingComment = FALSE;
2396     static int cmatch = 0; // continuation sequence match
2397     char *bp;
2398     char str[MSG_SIZ];
2399     int i, oldi;
2400     int buf_len;
2401     int next_out;
2402     int tkind;
2403     int backup;    /* [DM] For zippy color lines */
2404     char *p;
2405     char talker[MSG_SIZ]; // [HGM] chat
2406     int channel;
2407
2408     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2409
2410     if (appData.debugMode) {
2411       if (!error) {
2412         fprintf(debugFP, "<ICS: ");
2413         show_bytes(debugFP, data, count);
2414         fprintf(debugFP, "\n");
2415       }
2416     }
2417
2418     if (appData.debugMode) { int f = forwardMostMove;
2419         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2420                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2421                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2422     }
2423     if (count > 0) {
2424         /* If last read ended with a partial line that we couldn't parse,
2425            prepend it to the new read and try again. */
2426         if (leftover_len > 0) {
2427             for (i=0; i<leftover_len; i++)
2428               buf[i] = buf[leftover_start + i];
2429         }
2430
2431     /* copy new characters into the buffer */
2432     bp = buf + leftover_len;
2433     buf_len=leftover_len;
2434     for (i=0; i<count; i++)
2435     {
2436         // ignore these
2437         if (data[i] == '\r')
2438             continue;
2439
2440         // join lines split by ICS?
2441         if (!appData.noJoin)
2442         {
2443             /*
2444                 Joining just consists of finding matches against the
2445                 continuation sequence, and discarding that sequence
2446                 if found instead of copying it.  So, until a match
2447                 fails, there's nothing to do since it might be the
2448                 complete sequence, and thus, something we don't want
2449                 copied.
2450             */
2451             if (data[i] == cont_seq[cmatch])
2452             {
2453                 cmatch++;
2454                 if (cmatch == strlen(cont_seq))
2455                 {
2456                     cmatch = 0; // complete match.  just reset the counter
2457
2458                     /*
2459                         it's possible for the ICS to not include the space
2460                         at the end of the last word, making our [correct]
2461                         join operation fuse two separate words.  the server
2462                         does this when the space occurs at the width setting.
2463                     */
2464                     if (!buf_len || buf[buf_len-1] != ' ')
2465                     {
2466                         *bp++ = ' ';
2467                         buf_len++;
2468                     }
2469                 }
2470                 continue;
2471             }
2472             else if (cmatch)
2473             {
2474                 /*
2475                     match failed, so we have to copy what matched before
2476                     falling through and copying this character.  In reality,
2477                     this will only ever be just the newline character, but
2478                     it doesn't hurt to be precise.
2479                 */
2480                 strncpy(bp, cont_seq, cmatch);
2481                 bp += cmatch;
2482                 buf_len += cmatch;
2483                 cmatch = 0;
2484             }
2485         }
2486
2487         // copy this char
2488         *bp++ = data[i];
2489         buf_len++;
2490     }
2491
2492         buf[buf_len] = NULLCHAR;
2493 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2494         next_out = 0;
2495         leftover_start = 0;
2496
2497         i = 0;
2498         while (i < buf_len) {
2499             /* Deal with part of the TELNET option negotiation
2500                protocol.  We refuse to do anything beyond the
2501                defaults, except that we allow the WILL ECHO option,
2502                which ICS uses to turn off password echoing when we are
2503                directly connected to it.  We reject this option
2504                if localLineEditing mode is on (always on in xboard)
2505                and we are talking to port 23, which might be a real
2506                telnet server that will try to keep WILL ECHO on permanently.
2507              */
2508             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2509                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2510                 unsigned char option;
2511                 oldi = i;
2512                 switch ((unsigned char) buf[++i]) {
2513                   case TN_WILL:
2514                     if (appData.debugMode)
2515                       fprintf(debugFP, "\n<WILL ");
2516                     switch (option = (unsigned char) buf[++i]) {
2517                       case TN_ECHO:
2518                         if (appData.debugMode)
2519                           fprintf(debugFP, "ECHO ");
2520                         /* Reply only if this is a change, according
2521                            to the protocol rules. */
2522                         if (remoteEchoOption) break;
2523                         if (appData.localLineEditing &&
2524                             atoi(appData.icsPort) == TN_PORT) {
2525                             TelnetRequest(TN_DONT, TN_ECHO);
2526                         } else {
2527                             EchoOff();
2528                             TelnetRequest(TN_DO, TN_ECHO);
2529                             remoteEchoOption = TRUE;
2530                         }
2531                         break;
2532                       default:
2533                         if (appData.debugMode)
2534                           fprintf(debugFP, "%d ", option);
2535                         /* Whatever this is, we don't want it. */
2536                         TelnetRequest(TN_DONT, option);
2537                         break;
2538                     }
2539                     break;
2540                   case TN_WONT:
2541                     if (appData.debugMode)
2542                       fprintf(debugFP, "\n<WONT ");
2543                     switch (option = (unsigned char) buf[++i]) {
2544                       case TN_ECHO:
2545                         if (appData.debugMode)
2546                           fprintf(debugFP, "ECHO ");
2547                         /* Reply only if this is a change, according
2548                            to the protocol rules. */
2549                         if (!remoteEchoOption) break;
2550                         EchoOn();
2551                         TelnetRequest(TN_DONT, TN_ECHO);
2552                         remoteEchoOption = FALSE;
2553                         break;
2554                       default:
2555                         if (appData.debugMode)
2556                           fprintf(debugFP, "%d ", (unsigned char) option);
2557                         /* Whatever this is, it must already be turned
2558                            off, because we never agree to turn on
2559                            anything non-default, so according to the
2560                            protocol rules, we don't reply. */
2561                         break;
2562                     }
2563                     break;
2564                   case TN_DO:
2565                     if (appData.debugMode)
2566                       fprintf(debugFP, "\n<DO ");
2567                     switch (option = (unsigned char) buf[++i]) {
2568                       default:
2569                         /* Whatever this is, we refuse to do it. */
2570                         if (appData.debugMode)
2571                           fprintf(debugFP, "%d ", option);
2572                         TelnetRequest(TN_WONT, option);
2573                         break;
2574                     }
2575                     break;
2576                   case TN_DONT:
2577                     if (appData.debugMode)
2578                       fprintf(debugFP, "\n<DONT ");
2579                     switch (option = (unsigned char) buf[++i]) {
2580                       default:
2581                         if (appData.debugMode)
2582                           fprintf(debugFP, "%d ", option);
2583                         /* Whatever this is, we are already not doing
2584                            it, because we never agree to do anything
2585                            non-default, so according to the protocol
2586                            rules, we don't reply. */
2587                         break;
2588                     }
2589                     break;
2590                   case TN_IAC:
2591                     if (appData.debugMode)
2592                       fprintf(debugFP, "\n<IAC ");
2593                     /* Doubled IAC; pass it through */
2594                     i--;
2595                     break;
2596                   default:
2597                     if (appData.debugMode)
2598                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2599                     /* Drop all other telnet commands on the floor */
2600                     break;
2601                 }
2602                 if (oldi > next_out)
2603                   SendToPlayer(&buf[next_out], oldi - next_out);
2604                 if (++i > next_out)
2605                   next_out = i;
2606                 continue;
2607             }
2608
2609             /* OK, this at least will *usually* work */
2610             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2611                 loggedOn = TRUE;
2612             }
2613
2614             if (loggedOn && !intfSet) {
2615                 if (ics_type == ICS_ICC) {
2616                   snprintf(str, MSG_SIZ,
2617                           "/set-quietly interface %s\n/set-quietly style 12\n",
2618                           programVersion);
2619                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2620                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2621                 } else if (ics_type == ICS_CHESSNET) {
2622                   snprintf(str, MSG_SIZ, "/style 12\n");
2623                 } else {
2624                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2625                   strcat(str, programVersion);
2626                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2627                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2628                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2629 #ifdef WIN32
2630                   strcat(str, "$iset nohighlight 1\n");
2631 #endif
2632                   strcat(str, "$iset lock 1\n$style 12\n");
2633                 }
2634                 SendToICS(str);
2635                 NotifyFrontendLogin();
2636                 intfSet = TRUE;
2637             }
2638
2639             if (started == STARTED_COMMENT) {
2640                 /* Accumulate characters in comment */
2641                 parse[parse_pos++] = buf[i];
2642                 if (buf[i] == '\n') {
2643                     parse[parse_pos] = NULLCHAR;
2644                     if(chattingPartner>=0) {
2645                         char mess[MSG_SIZ];
2646                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2647                         OutputChatMessage(chattingPartner, mess);
2648                         chattingPartner = -1;
2649                         next_out = i+1; // [HGM] suppress printing in ICS window
2650                     } else
2651                     if(!suppressKibitz) // [HGM] kibitz
2652                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2653                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2654                         int nrDigit = 0, nrAlph = 0, j;
2655                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2656                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2657                         parse[parse_pos] = NULLCHAR;
2658                         // try to be smart: if it does not look like search info, it should go to
2659                         // ICS interaction window after all, not to engine-output window.
2660                         for(j=0; j<parse_pos; j++) { // count letters and digits
2661                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2662                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2663                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2664                         }
2665                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2666                             int depth=0; float score;
2667                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2668                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2669                                 pvInfoList[forwardMostMove-1].depth = depth;
2670                                 pvInfoList[forwardMostMove-1].score = 100*score;
2671                             }
2672                             OutputKibitz(suppressKibitz, parse);
2673                         } else {
2674                             char tmp[MSG_SIZ];
2675                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2676                             SendToPlayer(tmp, strlen(tmp));
2677                         }
2678                         next_out = i+1; // [HGM] suppress printing in ICS window
2679                     }
2680                     started = STARTED_NONE;
2681                 } else {
2682                     /* Don't match patterns against characters in comment */
2683                     i++;
2684                     continue;
2685                 }
2686             }
2687             if (started == STARTED_CHATTER) {
2688                 if (buf[i] != '\n') {
2689                     /* Don't match patterns against characters in chatter */
2690                     i++;
2691                     continue;
2692                 }
2693                 started = STARTED_NONE;
2694                 if(suppressKibitz) next_out = i+1;
2695             }
2696
2697             /* Kludge to deal with rcmd protocol */
2698             if (firstTime && looking_at(buf, &i, "\001*")) {
2699                 DisplayFatalError(&buf[1], 0, 1);
2700                 continue;
2701             } else {
2702                 firstTime = FALSE;
2703             }
2704
2705             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2706                 ics_type = ICS_ICC;
2707                 ics_prefix = "/";
2708                 if (appData.debugMode)
2709                   fprintf(debugFP, "ics_type %d\n", ics_type);
2710                 continue;
2711             }
2712             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2713                 ics_type = ICS_FICS;
2714                 ics_prefix = "$";
2715                 if (appData.debugMode)
2716                   fprintf(debugFP, "ics_type %d\n", ics_type);
2717                 continue;
2718             }
2719             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2720                 ics_type = ICS_CHESSNET;
2721                 ics_prefix = "/";
2722                 if (appData.debugMode)
2723                   fprintf(debugFP, "ics_type %d\n", ics_type);
2724                 continue;
2725             }
2726
2727             if (!loggedOn &&
2728                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2729                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2730                  looking_at(buf, &i, "will be \"*\""))) {
2731               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2732               continue;
2733             }
2734
2735             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2736               char buf[MSG_SIZ];
2737               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2738               DisplayIcsInteractionTitle(buf);
2739               have_set_title = TRUE;
2740             }
2741
2742             /* skip finger notes */
2743             if (started == STARTED_NONE &&
2744                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2745                  (buf[i] == '1' && buf[i+1] == '0')) &&
2746                 buf[i+2] == ':' && buf[i+3] == ' ') {
2747               started = STARTED_CHATTER;
2748               i += 3;
2749               continue;
2750             }
2751
2752             oldi = i;
2753             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2754             if(appData.seekGraph) {
2755                 if(soughtPending && MatchSoughtLine(buf+i)) {
2756                     i = strstr(buf+i, "rated") - buf;
2757                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2758                     next_out = leftover_start = i;
2759                     started = STARTED_CHATTER;
2760                     suppressKibitz = TRUE;
2761                     continue;
2762                 }
2763                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2764                         && looking_at(buf, &i, "* ads displayed")) {
2765                     soughtPending = FALSE;
2766                     seekGraphUp = TRUE;
2767                     DrawSeekGraph();
2768                     continue;
2769                 }
2770                 if(appData.autoRefresh) {
2771                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2772                         int s = (ics_type == ICS_ICC); // ICC format differs
2773                         if(seekGraphUp)
2774                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2775                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2776                         looking_at(buf, &i, "*% "); // eat prompt
2777                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2778                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2779                         next_out = i; // suppress
2780                         continue;
2781                     }
2782                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2783                         char *p = star_match[0];
2784                         while(*p) {
2785                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2786                             while(*p && *p++ != ' '); // next
2787                         }
2788                         looking_at(buf, &i, "*% "); // eat prompt
2789                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2790                         next_out = i;
2791                         continue;
2792                     }
2793                 }
2794             }
2795
2796             /* skip formula vars */
2797             if (started == STARTED_NONE &&
2798                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2799               started = STARTED_CHATTER;
2800               i += 3;
2801               continue;
2802             }
2803
2804             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2805             if (appData.autoKibitz && started == STARTED_NONE &&
2806                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2807                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2808                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2809                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2810                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2811                         suppressKibitz = TRUE;
2812                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2813                         next_out = i;
2814                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2815                                 && (gameMode == IcsPlayingWhite)) ||
2816                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2817                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2818                             started = STARTED_CHATTER; // own kibitz we simply discard
2819                         else {
2820                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2821                             parse_pos = 0; parse[0] = NULLCHAR;
2822                             savingComment = TRUE;
2823                             suppressKibitz = gameMode != IcsObserving ? 2 :
2824                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2825                         }
2826                         continue;
2827                 } else
2828                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2829                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2830                          && atoi(star_match[0])) {
2831                     // suppress the acknowledgements of our own autoKibitz
2832                     char *p;
2833                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2834                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2835                     SendToPlayer(star_match[0], strlen(star_match[0]));
2836                     if(looking_at(buf, &i, "*% ")) // eat prompt
2837                         suppressKibitz = FALSE;
2838                     next_out = i;
2839                     continue;
2840                 }
2841             } // [HGM] kibitz: end of patch
2842
2843             // [HGM] chat: intercept tells by users for which we have an open chat window
2844             channel = -1;
2845             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2846                                            looking_at(buf, &i, "* whispers:") ||
2847                                            looking_at(buf, &i, "* kibitzes:") ||
2848                                            looking_at(buf, &i, "* shouts:") ||
2849                                            looking_at(buf, &i, "* c-shouts:") ||
2850                                            looking_at(buf, &i, "--> * ") ||
2851                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2852                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2853                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2854                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2855                 int p;
2856                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2857                 chattingPartner = -1;
2858
2859                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2860                 for(p=0; p<MAX_CHAT; p++) {
2861                     if(channel == atoi(chatPartner[p])) {
2862                     talker[0] = '['; strcat(talker, "] ");
2863                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2864                     chattingPartner = p; break;
2865                     }
2866                 } else
2867                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2868                 for(p=0; p<MAX_CHAT; p++) {
2869                     if(!strcmp("kibitzes", chatPartner[p])) {
2870                         talker[0] = '['; strcat(talker, "] ");
2871                         chattingPartner = p; break;
2872                     }
2873                 } else
2874                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2875                 for(p=0; p<MAX_CHAT; p++) {
2876                     if(!strcmp("whispers", chatPartner[p])) {
2877                         talker[0] = '['; strcat(talker, "] ");
2878                         chattingPartner = p; break;
2879                     }
2880                 } else
2881                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2882                   if(buf[i-8] == '-' && buf[i-3] == 't')
2883                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2884                     if(!strcmp("c-shouts", chatPartner[p])) {
2885                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2886                         chattingPartner = p; break;
2887                     }
2888                   }
2889                   if(chattingPartner < 0)
2890                   for(p=0; p<MAX_CHAT; p++) {
2891                     if(!strcmp("shouts", chatPartner[p])) {
2892                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2893                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2894                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2895                         chattingPartner = p; break;
2896                     }
2897                   }
2898                 }
2899                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2900                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2901                     talker[0] = 0; Colorize(ColorTell, FALSE);
2902                     chattingPartner = p; break;
2903                 }
2904                 if(chattingPartner<0) i = oldi; else {
2905                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2906                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2907                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2908                     started = STARTED_COMMENT;
2909                     parse_pos = 0; parse[0] = NULLCHAR;
2910                     savingComment = 3 + chattingPartner; // counts as TRUE
2911                     suppressKibitz = TRUE;
2912                     continue;
2913                 }
2914             } // [HGM] chat: end of patch
2915
2916             if (appData.zippyTalk || appData.zippyPlay) {
2917                 /* [DM] Backup address for color zippy lines */
2918                 backup = i;
2919 #if ZIPPY
2920                if (loggedOn == TRUE)
2921                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2922                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2923 #endif
2924             } // [DM] 'else { ' deleted
2925                 if (
2926                     /* Regular tells and says */
2927                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2928                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2929                     looking_at(buf, &i, "* says: ") ||
2930                     /* Don't color "message" or "messages" output */
2931                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2932                     looking_at(buf, &i, "*. * at *:*: ") ||
2933                     looking_at(buf, &i, "--* (*:*): ") ||
2934                     /* Message notifications (same color as tells) */
2935                     looking_at(buf, &i, "* has left a message ") ||
2936                     looking_at(buf, &i, "* just sent you a message:\n") ||
2937                     /* Whispers and kibitzes */
2938                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2939                     looking_at(buf, &i, "* kibitzes: ") ||
2940                     /* Channel tells */
2941                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2942
2943                   if (tkind == 1 && strchr(star_match[0], ':')) {
2944                       /* Avoid "tells you:" spoofs in channels */
2945                      tkind = 3;
2946                   }
2947                   if (star_match[0][0] == NULLCHAR ||
2948                       strchr(star_match[0], ' ') ||
2949                       (tkind == 3 && strchr(star_match[1], ' '))) {
2950                     /* Reject bogus matches */
2951                     i = oldi;
2952                   } else {
2953                     if (appData.colorize) {
2954                       if (oldi > next_out) {
2955                         SendToPlayer(&buf[next_out], oldi - next_out);
2956                         next_out = oldi;
2957                       }
2958                       switch (tkind) {
2959                       case 1:
2960                         Colorize(ColorTell, FALSE);
2961                         curColor = ColorTell;
2962                         break;
2963                       case 2:
2964                         Colorize(ColorKibitz, FALSE);
2965                         curColor = ColorKibitz;
2966                         break;
2967                       case 3:
2968                         p = strrchr(star_match[1], '(');
2969                         if (p == NULL) {
2970                           p = star_match[1];
2971                         } else {
2972                           p++;
2973                         }
2974                         if (atoi(p) == 1) {
2975                           Colorize(ColorChannel1, FALSE);
2976                           curColor = ColorChannel1;
2977                         } else {
2978                           Colorize(ColorChannel, FALSE);
2979                           curColor = ColorChannel;
2980                         }
2981                         break;
2982                       case 5:
2983                         curColor = ColorNormal;
2984                         break;
2985                       }
2986                     }
2987                     if (started == STARTED_NONE && appData.autoComment &&
2988                         (gameMode == IcsObserving ||
2989                          gameMode == IcsPlayingWhite ||
2990                          gameMode == IcsPlayingBlack)) {
2991                       parse_pos = i - oldi;
2992                       memcpy(parse, &buf[oldi], parse_pos);
2993                       parse[parse_pos] = NULLCHAR;
2994                       started = STARTED_COMMENT;
2995                       savingComment = TRUE;
2996                     } else {
2997                       started = STARTED_CHATTER;
2998                       savingComment = FALSE;
2999                     }
3000                     loggedOn = TRUE;
3001                     continue;
3002                   }
3003                 }
3004
3005                 if (looking_at(buf, &i, "* s-shouts: ") ||
3006                     looking_at(buf, &i, "* c-shouts: ")) {
3007                     if (appData.colorize) {
3008                         if (oldi > next_out) {
3009                             SendToPlayer(&buf[next_out], oldi - next_out);
3010                             next_out = oldi;
3011                         }
3012                         Colorize(ColorSShout, FALSE);
3013                         curColor = ColorSShout;
3014                     }
3015                     loggedOn = TRUE;
3016                     started = STARTED_CHATTER;
3017                     continue;
3018                 }
3019
3020                 if (looking_at(buf, &i, "--->")) {
3021                     loggedOn = TRUE;
3022                     continue;
3023                 }
3024
3025                 if (looking_at(buf, &i, "* shouts: ") ||
3026                     looking_at(buf, &i, "--> ")) {
3027                     if (appData.colorize) {
3028                         if (oldi > next_out) {
3029                             SendToPlayer(&buf[next_out], oldi - next_out);
3030                             next_out = oldi;
3031                         }
3032                         Colorize(ColorShout, FALSE);
3033                         curColor = ColorShout;
3034                     }
3035                     loggedOn = TRUE;
3036                     started = STARTED_CHATTER;
3037                     continue;
3038                 }
3039
3040                 if (looking_at( buf, &i, "Challenge:")) {
3041                     if (appData.colorize) {
3042                         if (oldi > next_out) {
3043                             SendToPlayer(&buf[next_out], oldi - next_out);
3044                             next_out = oldi;
3045                         }
3046                         Colorize(ColorChallenge, FALSE);
3047                         curColor = ColorChallenge;
3048                     }
3049                     loggedOn = TRUE;
3050                     continue;
3051                 }
3052
3053                 if (looking_at(buf, &i, "* offers you") ||
3054                     looking_at(buf, &i, "* offers to be") ||
3055                     looking_at(buf, &i, "* would like to") ||
3056                     looking_at(buf, &i, "* requests to") ||
3057                     looking_at(buf, &i, "Your opponent offers") ||
3058                     looking_at(buf, &i, "Your opponent requests")) {
3059
3060                     if (appData.colorize) {
3061                         if (oldi > next_out) {
3062                             SendToPlayer(&buf[next_out], oldi - next_out);
3063                             next_out = oldi;
3064                         }
3065                         Colorize(ColorRequest, FALSE);
3066                         curColor = ColorRequest;
3067                     }
3068                     continue;
3069                 }
3070
3071                 if (looking_at(buf, &i, "* (*) seeking")) {
3072                     if (appData.colorize) {
3073                         if (oldi > next_out) {
3074                             SendToPlayer(&buf[next_out], oldi - next_out);
3075                             next_out = oldi;
3076                         }
3077                         Colorize(ColorSeek, FALSE);
3078                         curColor = ColorSeek;
3079                     }
3080                     continue;
3081             }
3082
3083             if (looking_at(buf, &i, "\\   ")) {
3084                 if (prevColor != ColorNormal) {
3085                     if (oldi > next_out) {
3086                         SendToPlayer(&buf[next_out], oldi - next_out);
3087                         next_out = oldi;
3088                     }
3089                     Colorize(prevColor, TRUE);
3090                     curColor = prevColor;
3091                 }
3092                 if (savingComment) {
3093                     parse_pos = i - oldi;
3094                     memcpy(parse, &buf[oldi], parse_pos);
3095                     parse[parse_pos] = NULLCHAR;
3096                     started = STARTED_COMMENT;
3097                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3098                         chattingPartner = savingComment - 3; // kludge to remember the box
3099                 } else {
3100                     started = STARTED_CHATTER;
3101                 }
3102                 continue;
3103             }
3104
3105             if (looking_at(buf, &i, "Black Strength :") ||
3106                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3107                 looking_at(buf, &i, "<10>") ||
3108                 looking_at(buf, &i, "#@#")) {
3109                 /* Wrong board style */
3110                 loggedOn = TRUE;
3111                 SendToICS(ics_prefix);
3112                 SendToICS("set style 12\n");
3113                 SendToICS(ics_prefix);
3114                 SendToICS("refresh\n");
3115                 continue;
3116             }
3117
3118             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3119                 ICSInitScript();
3120                 have_sent_ICS_logon = 1;
3121                 /* if we don't send the login/password via icsLogon, use special readline
3122                    code for it */
3123                 if (strlen(appData.icsLogon)==0)
3124                   {
3125                     sending_ICS_password = 0; // in case we come back to login
3126                     sending_ICS_login = 1;
3127                   };
3128                 continue;
3129             }
3130             /* need to shadow the password */
3131             if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3132               /* if we don't send the login/password via icsLogon, use special readline
3133                  code for it */
3134               if (strlen(appData.icsLogon)==0)
3135                 sending_ICS_password = 1;
3136               continue;
3137             }
3138
3139             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3140                 (looking_at(buf, &i, "\n<12> ") ||
3141                  looking_at(buf, &i, "<12> "))) {
3142                 loggedOn = TRUE;
3143                 if (oldi > next_out) {
3144                     SendToPlayer(&buf[next_out], oldi - next_out);
3145                 }
3146                 next_out = i;
3147                 started = STARTED_BOARD;
3148                 parse_pos = 0;
3149                 continue;
3150             }
3151
3152             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3153                 looking_at(buf, &i, "<b1> ")) {
3154                 if (oldi > next_out) {
3155                     SendToPlayer(&buf[next_out], oldi - next_out);
3156                 }
3157                 next_out = i;
3158                 started = STARTED_HOLDINGS;
3159                 parse_pos = 0;
3160                 continue;
3161             }
3162
3163             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3164                 loggedOn = TRUE;
3165                 /* Header for a move list -- first line */
3166
3167                 switch (ics_getting_history) {
3168                   case H_FALSE:
3169                     switch (gameMode) {
3170                       case IcsIdle:
3171                       case BeginningOfGame:
3172                         /* User typed "moves" or "oldmoves" while we
3173                            were idle.  Pretend we asked for these
3174                            moves and soak them up so user can step
3175                            through them and/or save them.
3176                            */
3177                         Reset(FALSE, TRUE);
3178                         gameMode = IcsObserving;
3179                         ModeHighlight();
3180                         ics_gamenum = -1;
3181                         ics_getting_history = H_GOT_UNREQ_HEADER;
3182                         break;
3183                       case EditGame: /*?*/
3184                       case EditPosition: /*?*/
3185                         /* Should above feature work in these modes too? */
3186                         /* For now it doesn't */
3187                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3188                         break;
3189                       default:
3190                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3191                         break;
3192                     }
3193                     break;
3194                   case H_REQUESTED:
3195                     /* Is this the right one? */
3196                     if (gameInfo.white && gameInfo.black &&
3197                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3198                         strcmp(gameInfo.black, star_match[2]) == 0) {
3199                         /* All is well */
3200                         ics_getting_history = H_GOT_REQ_HEADER;
3201                     }
3202                     break;
3203                   case H_GOT_REQ_HEADER:
3204                   case H_GOT_UNREQ_HEADER:
3205                   case H_GOT_UNWANTED_HEADER:
3206                   case H_GETTING_MOVES:
3207                     /* Should not happen */
3208                     DisplayError(_("Error gathering move list: two headers"), 0);
3209                     ics_getting_history = H_FALSE;
3210                     break;
3211                 }
3212
3213                 /* Save player ratings into gameInfo if needed */
3214                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3215                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3216                     (gameInfo.whiteRating == -1 ||
3217                      gameInfo.blackRating == -1)) {
3218
3219                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3220                     gameInfo.blackRating = string_to_rating(star_match[3]);
3221                     if (appData.debugMode)
3222                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3223                               gameInfo.whiteRating, gameInfo.blackRating);
3224                 }
3225                 continue;
3226             }
3227
3228             if (looking_at(buf, &i,
3229               "* * match, initial time: * minute*, increment: * second")) {
3230                 /* Header for a move list -- second line */
3231                 /* Initial board will follow if this is a wild game */
3232                 if (gameInfo.event != NULL) free(gameInfo.event);
3233                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3234                 gameInfo.event = StrSave(str);
3235                 /* [HGM] we switched variant. Translate boards if needed. */
3236                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3237                 continue;
3238             }
3239
3240             if (looking_at(buf, &i, "Move  ")) {
3241                 /* Beginning of a move list */
3242                 switch (ics_getting_history) {
3243                   case H_FALSE:
3244                     /* Normally should not happen */
3245                     /* Maybe user hit reset while we were parsing */
3246                     break;
3247                   case H_REQUESTED:
3248                     /* Happens if we are ignoring a move list that is not
3249                      * the one we just requested.  Common if the user
3250                      * tries to observe two games without turning off
3251                      * getMoveList */
3252                     break;
3253                   case H_GETTING_MOVES:
3254                     /* Should not happen */
3255                     DisplayError(_("Error gathering move list: nested"), 0);
3256                     ics_getting_history = H_FALSE;
3257                     break;
3258                   case H_GOT_REQ_HEADER:
3259                     ics_getting_history = H_GETTING_MOVES;
3260                     started = STARTED_MOVES;
3261                     parse_pos = 0;
3262                     if (oldi > next_out) {
3263                         SendToPlayer(&buf[next_out], oldi - next_out);
3264                     }
3265                     break;
3266                   case H_GOT_UNREQ_HEADER:
3267                     ics_getting_history = H_GETTING_MOVES;
3268                     started = STARTED_MOVES_NOHIDE;
3269                     parse_pos = 0;
3270                     break;
3271                   case H_GOT_UNWANTED_HEADER:
3272                     ics_getting_history = H_FALSE;
3273                     break;
3274                 }
3275                 continue;
3276             }
3277
3278             if (looking_at(buf, &i, "% ") ||
3279                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3280                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3281                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3282                     soughtPending = FALSE;
3283                     seekGraphUp = TRUE;
3284                     DrawSeekGraph();
3285                 }
3286                 if(suppressKibitz) next_out = i;
3287                 savingComment = FALSE;
3288                 suppressKibitz = 0;
3289                 switch (started) {
3290                   case STARTED_MOVES:
3291                   case STARTED_MOVES_NOHIDE:
3292                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3293                     parse[parse_pos + i - oldi] = NULLCHAR;
3294                     ParseGameHistory(parse);
3295 #if ZIPPY
3296                     if (appData.zippyPlay && first.initDone) {
3297                         FeedMovesToProgram(&first, forwardMostMove);
3298                         if (gameMode == IcsPlayingWhite) {
3299                             if (WhiteOnMove(forwardMostMove)) {
3300                                 if (first.sendTime) {
3301                                   if (first.useColors) {
3302                                     SendToProgram("black\n", &first);
3303                                   }
3304                                   SendTimeRemaining(&first, TRUE);
3305                                 }
3306                                 if (first.useColors) {
3307                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3308                                 }
3309                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3310                                 first.maybeThinking = TRUE;
3311                             } else {
3312                                 if (first.usePlayother) {
3313                                   if (first.sendTime) {
3314                                     SendTimeRemaining(&first, TRUE);
3315                                   }
3316                                   SendToProgram("playother\n", &first);
3317                                   firstMove = FALSE;
3318                                 } else {
3319                                   firstMove = TRUE;
3320                                 }
3321                             }
3322                         } else if (gameMode == IcsPlayingBlack) {
3323                             if (!WhiteOnMove(forwardMostMove)) {
3324                                 if (first.sendTime) {
3325                                   if (first.useColors) {
3326                                     SendToProgram("white\n", &first);
3327                                   }
3328                                   SendTimeRemaining(&first, FALSE);
3329                                 }
3330                                 if (first.useColors) {
3331                                   SendToProgram("black\n", &first);
3332                                 }
3333                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3334                                 first.maybeThinking = TRUE;
3335                             } else {
3336                                 if (first.usePlayother) {
3337                                   if (first.sendTime) {
3338                                     SendTimeRemaining(&first, FALSE);
3339                                   }
3340                                   SendToProgram("playother\n", &first);
3341                                   firstMove = FALSE;
3342                                 } else {
3343                                   firstMove = TRUE;
3344                                 }
3345                             }
3346                         }
3347                     }
3348 #endif
3349                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3350                         /* Moves came from oldmoves or moves command
3351                            while we weren't doing anything else.
3352                            */
3353                         currentMove = forwardMostMove;
3354                         ClearHighlights();/*!!could figure this out*/
3355                         flipView = appData.flipView;
3356                         DrawPosition(TRUE, boards[currentMove]);
3357                         DisplayBothClocks();
3358                         snprintf(str, MSG_SIZ, "%s vs. %s",
3359                                 gameInfo.white, gameInfo.black);
3360                         DisplayTitle(str);
3361                         gameMode = IcsIdle;
3362                     } else {
3363                         /* Moves were history of an active game */
3364                         if (gameInfo.resultDetails != NULL) {
3365                             free(gameInfo.resultDetails);
3366                             gameInfo.resultDetails = NULL;
3367                         }
3368                     }
3369                     HistorySet(parseList, backwardMostMove,
3370                                forwardMostMove, currentMove-1);
3371                     DisplayMove(currentMove - 1);
3372                     if (started == STARTED_MOVES) next_out = i;
3373                     started = STARTED_NONE;
3374                     ics_getting_history = H_FALSE;
3375                     break;
3376
3377                   case STARTED_OBSERVE:
3378                     started = STARTED_NONE;
3379                     SendToICS(ics_prefix);
3380                     SendToICS("refresh\n");
3381                     break;
3382
3383                   default:
3384                     break;
3385                 }
3386                 if(bookHit) { // [HGM] book: simulate book reply
3387                     static char bookMove[MSG_SIZ]; // a bit generous?
3388
3389                     programStats.nodes = programStats.depth = programStats.time =
3390                     programStats.score = programStats.got_only_move = 0;
3391                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3392
3393                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3394                     strcat(bookMove, bookHit);
3395                     HandleMachineMove(bookMove, &first);
3396                 }
3397                 continue;
3398             }
3399
3400             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3401                  started == STARTED_HOLDINGS ||
3402                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3403                 /* Accumulate characters in move list or board */
3404                 parse[parse_pos++] = buf[i];
3405             }
3406
3407             /* Start of game messages.  Mostly we detect start of game
3408                when the first board image arrives.  On some versions
3409                of the ICS, though, we need to do a "refresh" after starting
3410                to observe in order to get the current board right away. */
3411             if (looking_at(buf, &i, "Adding game * to observation list")) {
3412                 started = STARTED_OBSERVE;
3413                 continue;
3414             }
3415
3416             /* Handle auto-observe */
3417             if (appData.autoObserve &&
3418                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3419                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3420                 char *player;
3421                 /* Choose the player that was highlighted, if any. */
3422                 if (star_match[0][0] == '\033' ||
3423                     star_match[1][0] != '\033') {
3424                     player = star_match[0];
3425                 } else {
3426                     player = star_match[2];
3427                 }
3428                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3429                         ics_prefix, StripHighlightAndTitle(player));
3430                 SendToICS(str);
3431
3432                 /* Save ratings from notify string */
3433                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3434                 player1Rating = string_to_rating(star_match[1]);
3435                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3436                 player2Rating = string_to_rating(star_match[3]);
3437
3438                 if (appData.debugMode)
3439                   fprintf(debugFP,
3440                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3441                           player1Name, player1Rating,
3442                           player2Name, player2Rating);
3443
3444                 continue;
3445             }
3446
3447             /* Deal with automatic examine mode after a game,
3448                and with IcsObserving -> IcsExamining transition */
3449             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3450                 looking_at(buf, &i, "has made you an examiner of game *")) {
3451
3452                 int gamenum = atoi(star_match[0]);
3453                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3454                     gamenum == ics_gamenum) {
3455                     /* We were already playing or observing this game;
3456                        no need to refetch history */
3457                     gameMode = IcsExamining;
3458                     if (pausing) {
3459                         pauseExamForwardMostMove = forwardMostMove;
3460                     } else if (currentMove < forwardMostMove) {
3461                         ForwardInner(forwardMostMove);
3462                     }
3463                 } else {
3464                     /* I don't think this case really can happen */
3465                     SendToICS(ics_prefix);
3466                     SendToICS("refresh\n");
3467                 }
3468                 continue;
3469             }
3470
3471             /* Error messages */
3472 //          if (ics_user_moved) {
3473             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3474                 if (looking_at(buf, &i, "Illegal move") ||
3475                     looking_at(buf, &i, "Not a legal move") ||
3476                     looking_at(buf, &i, "Your king is in check") ||
3477                     looking_at(buf, &i, "It isn't your turn") ||
3478                     looking_at(buf, &i, "It is not your move")) {
3479                     /* Illegal move */
3480                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3481                         currentMove = forwardMostMove-1;
3482                         DisplayMove(currentMove - 1); /* before DMError */
3483                         DrawPosition(FALSE, boards[currentMove]);
3484                         SwitchClocks(forwardMostMove-1); // [HGM] race
3485                         DisplayBothClocks();
3486                     }
3487                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3488                     ics_user_moved = 0;
3489                     continue;
3490                 }
3491             }
3492
3493             if (looking_at(buf, &i, "still have time") ||
3494                 looking_at(buf, &i, "not out of time") ||
3495                 looking_at(buf, &i, "either player is out of time") ||
3496                 looking_at(buf, &i, "has timeseal; checking")) {
3497                 /* We must have called his flag a little too soon */
3498                 whiteFlag = blackFlag = FALSE;
3499                 continue;
3500             }
3501
3502             if (looking_at(buf, &i, "added * seconds to") ||
3503                 looking_at(buf, &i, "seconds were added to")) {
3504                 /* Update the clocks */
3505                 SendToICS(ics_prefix);
3506                 SendToICS("refresh\n");
3507                 continue;
3508             }
3509
3510             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3511                 ics_clock_paused = TRUE;
3512                 StopClocks();
3513                 continue;
3514             }
3515
3516             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3517                 ics_clock_paused = FALSE;
3518                 StartClocks();
3519                 continue;
3520             }
3521
3522             /* Grab player ratings from the Creating: message.
3523                Note we have to check for the special case when
3524                the ICS inserts things like [white] or [black]. */
3525             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3526                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3527                 /* star_matches:
3528                    0    player 1 name (not necessarily white)
3529                    1    player 1 rating
3530                    2    empty, white, or black (IGNORED)
3531                    3    player 2 name (not necessarily black)
3532                    4    player 2 rating
3533
3534                    The names/ratings are sorted out when the game
3535                    actually starts (below).
3536                 */
3537                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3538                 player1Rating = string_to_rating(star_match[1]);
3539                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3540                 player2Rating = string_to_rating(star_match[4]);
3541
3542                 if (appData.debugMode)
3543                   fprintf(debugFP,
3544                           "Ratings from 'Creating:' %s %d, %s %d\n",
3545                           player1Name, player1Rating,
3546                           player2Name, player2Rating);
3547
3548                 continue;
3549             }
3550
3551             /* Improved generic start/end-of-game messages */
3552             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3553                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3554                 /* If tkind == 0: */
3555                 /* star_match[0] is the game number */
3556                 /*           [1] is the white player's name */
3557                 /*           [2] is the black player's name */
3558                 /* For end-of-game: */
3559                 /*           [3] is the reason for the game end */
3560                 /*           [4] is a PGN end game-token, preceded by " " */
3561                 /* For start-of-game: */
3562                 /*           [3] begins with "Creating" or "Continuing" */
3563                 /*           [4] is " *" or empty (don't care). */
3564                 int gamenum = atoi(star_match[0]);
3565                 char *whitename, *blackname, *why, *endtoken;
3566                 ChessMove endtype = EndOfFile;
3567
3568                 if (tkind == 0) {
3569                   whitename = star_match[1];
3570                   blackname = star_match[2];
3571                   why = star_match[3];
3572                   endtoken = star_match[4];
3573                 } else {
3574                   whitename = star_match[1];
3575                   blackname = star_match[3];
3576                   why = star_match[5];
3577                   endtoken = star_match[6];
3578                 }
3579
3580                 /* Game start messages */
3581                 if (strncmp(why, "Creating ", 9) == 0 ||
3582                     strncmp(why, "Continuing ", 11) == 0) {
3583                     gs_gamenum = gamenum;
3584                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3585                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3586 #if ZIPPY
3587                     if (appData.zippyPlay) {
3588                         ZippyGameStart(whitename, blackname);
3589                     }
3590 #endif /*ZIPPY*/
3591                     partnerBoardValid = FALSE; // [HGM] bughouse
3592                     continue;
3593                 }
3594
3595                 /* Game end messages */
3596                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3597                     ics_gamenum != gamenum) {
3598                     continue;
3599                 }
3600                 while (endtoken[0] == ' ') endtoken++;
3601                 switch (endtoken[0]) {
3602                   case '*':
3603                   default:
3604                     endtype = GameUnfinished;
3605                     break;
3606                   case '0':
3607                     endtype = BlackWins;
3608                     break;
3609                   case '1':
3610                     if (endtoken[1] == '/')
3611                       endtype = GameIsDrawn;
3612                     else
3613                       endtype = WhiteWins;
3614                     break;
3615                 }
3616                 GameEnds(endtype, why, GE_ICS);
3617 #if ZIPPY
3618                 if (appData.zippyPlay && first.initDone) {
3619                     ZippyGameEnd(endtype, why);
3620                     if (first.pr == NULL) {
3621                       /* Start the next process early so that we'll
3622                          be ready for the next challenge */
3623                       StartChessProgram(&first);
3624                     }
3625                     /* Send "new" early, in case this command takes
3626                        a long time to finish, so that we'll be ready
3627                        for the next challenge. */
3628                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3629                     Reset(TRUE, TRUE);
3630                 }
3631 #endif /*ZIPPY*/
3632                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3633                 continue;
3634             }
3635
3636             if (looking_at(buf, &i, "Removing game * from observation") ||
3637                 looking_at(buf, &i, "no longer observing game *") ||
3638                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3639                 if (gameMode == IcsObserving &&
3640                     atoi(star_match[0]) == ics_gamenum)
3641                   {
3642                       /* icsEngineAnalyze */
3643                       if (appData.icsEngineAnalyze) {
3644                             ExitAnalyzeMode();
3645                             ModeHighlight();
3646                       }
3647                       StopClocks();
3648                       gameMode = IcsIdle;
3649                       ics_gamenum = -1;
3650                       ics_user_moved = FALSE;
3651                   }
3652                 continue;
3653             }
3654
3655             if (looking_at(buf, &i, "no longer examining game *")) {
3656                 if (gameMode == IcsExamining &&
3657                     atoi(star_match[0]) == ics_gamenum)
3658                   {
3659                       gameMode = IcsIdle;
3660                       ics_gamenum = -1;
3661                       ics_user_moved = FALSE;
3662                   }
3663                 continue;
3664             }
3665
3666             /* Advance leftover_start past any newlines we find,
3667                so only partial lines can get reparsed */
3668             if (looking_at(buf, &i, "\n")) {
3669                 prevColor = curColor;
3670                 if (curColor != ColorNormal) {
3671                     if (oldi > next_out) {
3672                         SendToPlayer(&buf[next_out], oldi - next_out);
3673                         next_out = oldi;
3674                     }
3675                     Colorize(ColorNormal, FALSE);
3676                     curColor = ColorNormal;
3677                 }
3678                 if (started == STARTED_BOARD) {
3679                     started = STARTED_NONE;
3680                     parse[parse_pos] = NULLCHAR;
3681                     ParseBoard12(parse);
3682                     ics_user_moved = 0;
3683
3684                     /* Send premove here */
3685                     if (appData.premove) {
3686                       char str[MSG_SIZ];
3687                       if (currentMove == 0 &&
3688                           gameMode == IcsPlayingWhite &&
3689                           appData.premoveWhite) {
3690                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3691                         if (appData.debugMode)
3692                           fprintf(debugFP, "Sending premove:\n");
3693                         SendToICS(str);
3694                       } else if (currentMove == 1 &&
3695                                  gameMode == IcsPlayingBlack &&
3696                                  appData.premoveBlack) {
3697                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3698                         if (appData.debugMode)
3699                           fprintf(debugFP, "Sending premove:\n");
3700                         SendToICS(str);
3701                       } else if (gotPremove) {
3702                         gotPremove = 0;
3703                         ClearPremoveHighlights();
3704                         if (appData.debugMode)
3705                           fprintf(debugFP, "Sending premove:\n");
3706                           UserMoveEvent(premoveFromX, premoveFromY,
3707                                         premoveToX, premoveToY,
3708                                         premovePromoChar);
3709                       }
3710                     }
3711
3712                     /* Usually suppress following prompt */
3713                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3714                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3715                         if (looking_at(buf, &i, "*% ")) {
3716                             savingComment = FALSE;
3717                             suppressKibitz = 0;
3718                         }
3719                     }
3720                     next_out = i;
3721                 } else if (started == STARTED_HOLDINGS) {
3722                     int gamenum;
3723                     char new_piece[MSG_SIZ];
3724                     started = STARTED_NONE;
3725                     parse[parse_pos] = NULLCHAR;
3726                     if (appData.debugMode)
3727                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3728                                                         parse, currentMove);
3729                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3730                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3731                         if (gameInfo.variant == VariantNormal) {
3732                           /* [HGM] We seem to switch variant during a game!
3733                            * Presumably no holdings were displayed, so we have
3734                            * to move the position two files to the right to
3735                            * create room for them!
3736                            */
3737                           VariantClass newVariant;
3738                           switch(gameInfo.boardWidth) { // base guess on board width
3739                                 case 9:  newVariant = VariantShogi; break;
3740                                 case 10: newVariant = VariantGreat; break;
3741                                 default: newVariant = VariantCrazyhouse; break;
3742                           }
3743                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3744                           /* Get a move list just to see the header, which
3745                              will tell us whether this is really bug or zh */
3746                           if (ics_getting_history == H_FALSE) {
3747                             ics_getting_history = H_REQUESTED;
3748                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3749                             SendToICS(str);
3750                           }
3751                         }
3752                         new_piece[0] = NULLCHAR;
3753                         sscanf(parse, "game %d white [%s black [%s <- %s",
3754                                &gamenum, white_holding, black_holding,
3755                                new_piece);
3756                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3757                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3758                         /* [HGM] copy holdings to board holdings area */
3759                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3760                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3761                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3762 #if ZIPPY
3763                         if (appData.zippyPlay && first.initDone) {
3764                             ZippyHoldings(white_holding, black_holding,
3765                                           new_piece);
3766                         }
3767 #endif /*ZIPPY*/
3768                         if (tinyLayout || smallLayout) {
3769                             char wh[16], bh[16];
3770                             PackHolding(wh, white_holding);
3771                             PackHolding(bh, black_holding);
3772                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3773                                     gameInfo.white, gameInfo.black);
3774                         } else {
3775                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3776                                     gameInfo.white, white_holding,
3777                                     gameInfo.black, black_holding);
3778                         }
3779                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3780                         DrawPosition(FALSE, boards[currentMove]);
3781                         DisplayTitle(str);
3782                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3783                         sscanf(parse, "game %d white [%s black [%s <- %s",
3784                                &gamenum, white_holding, black_holding,
3785                                new_piece);
3786                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3787                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3788                         /* [HGM] copy holdings to partner-board holdings area */
3789                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3790                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3791                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3792                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3793                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3794                       }
3795                     }
3796                     /* Suppress following prompt */
3797                     if (looking_at(buf, &i, "*% ")) {
3798                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3799                         savingComment = FALSE;
3800                         suppressKibitz = 0;
3801                     }
3802                     next_out = i;
3803                 }
3804                 continue;
3805             }
3806
3807             i++;                /* skip unparsed character and loop back */
3808         }
3809
3810         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3811 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3812 //          SendToPlayer(&buf[next_out], i - next_out);
3813             started != STARTED_HOLDINGS && leftover_start > next_out) {
3814             SendToPlayer(&buf[next_out], leftover_start - next_out);
3815             next_out = i;
3816         }
3817
3818         leftover_len = buf_len - leftover_start;
3819         /* if buffer ends with something we couldn't parse,
3820            reparse it after appending the next read */
3821
3822     } else if (count == 0) {
3823         RemoveInputSource(isr);
3824         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3825     } else {
3826         DisplayFatalError(_("Error reading from ICS"), error, 1);
3827     }
3828 }
3829
3830
3831 /* Board style 12 looks like this:
3832
3833    <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
3834
3835  * The "<12> " is stripped before it gets to this routine.  The two
3836  * trailing 0's (flip state and clock ticking) are later addition, and
3837  * some chess servers may not have them, or may have only the first.
3838  * Additional trailing fields may be added in the future.
3839  */
3840
3841 #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"
3842
3843 #define RELATION_OBSERVING_PLAYED    0
3844 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3845 #define RELATION_PLAYING_MYMOVE      1
3846 #define RELATION_PLAYING_NOTMYMOVE  -1
3847 #define RELATION_EXAMINING           2
3848 #define RELATION_ISOLATED_BOARD     -3
3849 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3850
3851 void
3852 ParseBoard12(string)
3853      char *string;
3854 {
3855     GameMode newGameMode;
3856     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3857     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3858     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3859     char to_play, board_chars[200];
3860     char move_str[500], str[500], elapsed_time[500];
3861     char black[32], white[32];
3862     Board board;
3863     int prevMove = currentMove;
3864     int ticking = 2;
3865     ChessMove moveType;
3866     int fromX, fromY, toX, toY;
3867     char promoChar;
3868     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3869     char *bookHit = NULL; // [HGM] book
3870     Boolean weird = FALSE, reqFlag = FALSE;
3871
3872     fromX = fromY = toX = toY = -1;
3873
3874     newGame = FALSE;
3875
3876     if (appData.debugMode)
3877       fprintf(debugFP, _("Parsing board: %s\n"), string);
3878
3879     move_str[0] = NULLCHAR;
3880     elapsed_time[0] = NULLCHAR;
3881     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3882         int  i = 0, j;
3883         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3884             if(string[i] == ' ') { ranks++; files = 0; }
3885             else files++;
3886             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3887             i++;
3888         }
3889         for(j = 0; j <i; j++) board_chars[j] = string[j];
3890         board_chars[i] = '\0';
3891         string += i + 1;
3892     }
3893     n = sscanf(string, PATTERN, &to_play, &double_push,
3894                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3895                &gamenum, white, black, &relation, &basetime, &increment,
3896                &white_stren, &black_stren, &white_time, &black_time,
3897                &moveNum, str, elapsed_time, move_str, &ics_flip,
3898                &ticking);
3899
3900     if (n < 21) {
3901         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3902         DisplayError(str, 0);
3903         return;
3904     }
3905
3906     /* Convert the move number to internal form */
3907     moveNum = (moveNum - 1) * 2;
3908     if (to_play == 'B') moveNum++;
3909     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3910       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3911                         0, 1);
3912       return;
3913     }
3914
3915     switch (relation) {
3916       case RELATION_OBSERVING_PLAYED:
3917       case RELATION_OBSERVING_STATIC:
3918         if (gamenum == -1) {
3919             /* Old ICC buglet */
3920             relation = RELATION_OBSERVING_STATIC;
3921         }
3922         newGameMode = IcsObserving;
3923         break;
3924       case RELATION_PLAYING_MYMOVE:
3925       case RELATION_PLAYING_NOTMYMOVE:
3926         newGameMode =
3927           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3928             IcsPlayingWhite : IcsPlayingBlack;
3929         break;
3930       case RELATION_EXAMINING:
3931         newGameMode = IcsExamining;
3932         break;
3933       case RELATION_ISOLATED_BOARD:
3934       default:
3935         /* Just display this board.  If user was doing something else,
3936            we will forget about it until the next board comes. */
3937         newGameMode = IcsIdle;
3938         break;
3939       case RELATION_STARTING_POSITION:
3940         newGameMode = gameMode;
3941         break;
3942     }
3943
3944     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3945          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3946       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3947       char *toSqr;
3948       for (k = 0; k < ranks; k++) {
3949         for (j = 0; j < files; j++)
3950           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3951         if(gameInfo.holdingsWidth > 1) {
3952              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3953              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3954         }
3955       }
3956       CopyBoard(partnerBoard, board);
3957       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3958         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3959         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3960       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3961       if(toSqr = strchr(str, '-')) {
3962         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3963         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3964       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3965       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3966       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3967       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3968       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3969       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3970                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3971       DisplayMessage(partnerStatus, "");
3972         partnerBoardValid = TRUE;
3973       return;
3974     }
3975
3976     /* Modify behavior for initial board display on move listing
3977        of wild games.
3978        */
3979     switch (ics_getting_history) {
3980       case H_FALSE:
3981       case H_REQUESTED:
3982         break;
3983       case H_GOT_REQ_HEADER:
3984       case H_GOT_UNREQ_HEADER:
3985         /* This is the initial position of the current game */
3986         gamenum = ics_gamenum;
3987         moveNum = 0;            /* old ICS bug workaround */
3988         if (to_play == 'B') {
3989           startedFromSetupPosition = TRUE;
3990           blackPlaysFirst = TRUE;
3991           moveNum = 1;
3992           if (forwardMostMove == 0) forwardMostMove = 1;
3993           if (backwardMostMove == 0) backwardMostMove = 1;
3994           if (currentMove == 0) currentMove = 1;
3995         }
3996         newGameMode = gameMode;
3997         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3998         break;
3999       case H_GOT_UNWANTED_HEADER:
4000         /* This is an initial board that we don't want */
4001         return;
4002       case H_GETTING_MOVES:
4003         /* Should not happen */
4004         DisplayError(_("Error gathering move list: extra board"), 0);
4005         ics_getting_history = H_FALSE;
4006         return;
4007     }
4008
4009    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4010                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4011      /* [HGM] We seem to have switched variant unexpectedly
4012       * Try to guess new variant from board size
4013       */
4014           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4015           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4016           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4017           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4018           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4019           if(!weird) newVariant = VariantNormal;
4020           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4021           /* Get a move list just to see the header, which
4022              will tell us whether this is really bug or zh */
4023           if (ics_getting_history == H_FALSE) {
4024             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4025             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4026             SendToICS(str);
4027           }
4028     }
4029
4030     /* Take action if this is the first board of a new game, or of a
4031        different game than is currently being displayed.  */
4032     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4033         relation == RELATION_ISOLATED_BOARD) {
4034
4035         /* Forget the old game and get the history (if any) of the new one */
4036         if (gameMode != BeginningOfGame) {
4037           Reset(TRUE, TRUE);
4038         }
4039         newGame = TRUE;
4040         if (appData.autoRaiseBoard) BoardToTop();
4041         prevMove = -3;
4042         if (gamenum == -1) {
4043             newGameMode = IcsIdle;
4044         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4045                    appData.getMoveList && !reqFlag) {
4046             /* Need to get game history */
4047             ics_getting_history = H_REQUESTED;
4048             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4049             SendToICS(str);
4050         }
4051
4052         /* Initially flip the board to have black on the bottom if playing
4053            black or if the ICS flip flag is set, but let the user change
4054            it with the Flip View button. */
4055         flipView = appData.autoFlipView ?
4056           (newGameMode == IcsPlayingBlack) || ics_flip :
4057           appData.flipView;
4058
4059         /* Done with values from previous mode; copy in new ones */
4060         gameMode = newGameMode;
4061         ModeHighlight();
4062         ics_gamenum = gamenum;
4063         if (gamenum == gs_gamenum) {
4064             int klen = strlen(gs_kind);
4065             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4066             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4067             gameInfo.event = StrSave(str);
4068         } else {
4069             gameInfo.event = StrSave("ICS game");
4070         }
4071         gameInfo.site = StrSave(appData.icsHost);
4072         gameInfo.date = PGNDate();
4073         gameInfo.round = StrSave("-");
4074         gameInfo.white = StrSave(white);
4075         gameInfo.black = StrSave(black);
4076         timeControl = basetime * 60 * 1000;
4077         timeControl_2 = 0;
4078         timeIncrement = increment * 1000;
4079         movesPerSession = 0;
4080         gameInfo.timeControl = TimeControlTagValue();
4081         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4082   if (appData.debugMode) {
4083     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4084     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4085     setbuf(debugFP, NULL);
4086   }
4087
4088         gameInfo.outOfBook = NULL;
4089
4090         /* Do we have the ratings? */
4091         if (strcmp(player1Name, white) == 0 &&
4092             strcmp(player2Name, black) == 0) {
4093             if (appData.debugMode)
4094               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4095                       player1Rating, player2Rating);
4096             gameInfo.whiteRating = player1Rating;
4097             gameInfo.blackRating = player2Rating;
4098         } else if (strcmp(player2Name, white) == 0 &&
4099                    strcmp(player1Name, black) == 0) {
4100             if (appData.debugMode)
4101               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4102                       player2Rating, player1Rating);
4103             gameInfo.whiteRating = player2Rating;
4104             gameInfo.blackRating = player1Rating;
4105         }
4106         player1Name[0] = player2Name[0] = NULLCHAR;
4107
4108         /* Silence shouts if requested */
4109         if (appData.quietPlay &&
4110             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4111             SendToICS(ics_prefix);
4112             SendToICS("set shout 0\n");
4113         }
4114     }
4115
4116     /* Deal with midgame name changes */
4117     if (!newGame) {
4118         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4119             if (gameInfo.white) free(gameInfo.white);
4120             gameInfo.white = StrSave(white);
4121         }
4122         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4123             if (gameInfo.black) free(gameInfo.black);
4124             gameInfo.black = StrSave(black);
4125         }
4126     }
4127
4128     /* Throw away game result if anything actually changes in examine mode */
4129     if (gameMode == IcsExamining && !newGame) {
4130         gameInfo.result = GameUnfinished;
4131         if (gameInfo.resultDetails != NULL) {
4132             free(gameInfo.resultDetails);
4133             gameInfo.resultDetails = NULL;
4134         }
4135     }
4136
4137     /* In pausing && IcsExamining mode, we ignore boards coming
4138        in if they are in a different variation than we are. */
4139     if (pauseExamInvalid) return;
4140     if (pausing && gameMode == IcsExamining) {
4141         if (moveNum <= pauseExamForwardMostMove) {
4142             pauseExamInvalid = TRUE;
4143             forwardMostMove = pauseExamForwardMostMove;
4144             return;
4145         }
4146     }
4147
4148   if (appData.debugMode) {
4149     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4150   }
4151     /* Parse the board */
4152     for (k = 0; k < ranks; k++) {
4153       for (j = 0; j < files; j++)
4154         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4155       if(gameInfo.holdingsWidth > 1) {
4156            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4157            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4158       }
4159     }
4160     CopyBoard(boards[moveNum], board);
4161     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4162     if (moveNum == 0) {
4163         startedFromSetupPosition =
4164           !CompareBoards(board, initialPosition);
4165         if(startedFromSetupPosition)
4166             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4167     }
4168
4169     /* [HGM] Set castling rights. Take the outermost Rooks,
4170        to make it also work for FRC opening positions. Note that board12
4171        is really defective for later FRC positions, as it has no way to
4172        indicate which Rook can castle if they are on the same side of King.
4173        For the initial position we grant rights to the outermost Rooks,
4174        and remember thos rights, and we then copy them on positions
4175        later in an FRC game. This means WB might not recognize castlings with
4176        Rooks that have moved back to their original position as illegal,
4177        but in ICS mode that is not its job anyway.
4178     */
4179     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4180     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4181
4182         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4183             if(board[0][i] == WhiteRook) j = i;
4184         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4185         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4186             if(board[0][i] == WhiteRook) j = i;
4187         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4188         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4189             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4190         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4191         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4192             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4193         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4194
4195         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4196         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4197             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4198         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4199             if(board[BOARD_HEIGHT-1][k] == bKing)
4200                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4201         if(gameInfo.variant == VariantTwoKings) {
4202             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4203             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4204             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4205         }
4206     } else { int r;
4207         r = boards[moveNum][CASTLING][0] = initialRights[0];
4208         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4209         r = boards[moveNum][CASTLING][1] = initialRights[1];
4210         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4211         r = boards[moveNum][CASTLING][3] = initialRights[3];
4212         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4213         r = boards[moveNum][CASTLING][4] = initialRights[4];
4214         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4215         /* wildcastle kludge: always assume King has rights */
4216         r = boards[moveNum][CASTLING][2] = initialRights[2];
4217         r = boards[moveNum][CASTLING][5] = initialRights[5];
4218     }
4219     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4220     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4221
4222
4223     if (ics_getting_history == H_GOT_REQ_HEADER ||
4224         ics_getting_history == H_GOT_UNREQ_HEADER) {
4225         /* This was an initial position from a move list, not
4226            the current position */
4227         return;
4228     }
4229
4230     /* Update currentMove and known move number limits */
4231     newMove = newGame || moveNum > forwardMostMove;
4232
4233     if (newGame) {
4234         forwardMostMove = backwardMostMove = currentMove = moveNum;
4235         if (gameMode == IcsExamining && moveNum == 0) {
4236           /* Workaround for ICS limitation: we are not told the wild
4237              type when starting to examine a game.  But if we ask for
4238              the move list, the move list header will tell us */
4239             ics_getting_history = H_REQUESTED;
4240             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4241             SendToICS(str);
4242         }
4243     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4244                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4245 #if ZIPPY
4246         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4247         /* [HGM] applied this also to an engine that is silently watching        */
4248         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4249             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4250             gameInfo.variant == currentlyInitializedVariant) {
4251           takeback = forwardMostMove - moveNum;
4252           for (i = 0; i < takeback; i++) {
4253             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4254             SendToProgram("undo\n", &first);
4255           }
4256         }
4257 #endif
4258
4259         forwardMostMove = moveNum;
4260         if (!pausing || currentMove > forwardMostMove)
4261           currentMove = forwardMostMove;
4262     } else {
4263         /* New part of history that is not contiguous with old part */
4264         if (pausing && gameMode == IcsExamining) {
4265             pauseExamInvalid = TRUE;
4266             forwardMostMove = pauseExamForwardMostMove;
4267             return;
4268         }
4269         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4270 #if ZIPPY
4271             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4272                 // [HGM] when we will receive the move list we now request, it will be
4273                 // fed to the engine from the first move on. So if the engine is not
4274                 // in the initial position now, bring it there.
4275                 InitChessProgram(&first, 0);
4276             }
4277 #endif
4278             ics_getting_history = H_REQUESTED;
4279             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4280             SendToICS(str);
4281         }
4282         forwardMostMove = backwardMostMove = currentMove = moveNum;
4283     }
4284
4285     /* Update the clocks */
4286     if (strchr(elapsed_time, '.')) {
4287       /* Time is in ms */
4288       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4289       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4290     } else {
4291       /* Time is in seconds */
4292       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4293       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4294     }
4295
4296
4297 #if ZIPPY
4298     if (appData.zippyPlay && newGame &&
4299         gameMode != IcsObserving && gameMode != IcsIdle &&
4300         gameMode != IcsExamining)
4301       ZippyFirstBoard(moveNum, basetime, increment);
4302 #endif
4303
4304     /* Put the move on the move list, first converting
4305        to canonical algebraic form. */
4306     if (moveNum > 0) {
4307   if (appData.debugMode) {
4308     if (appData.debugMode) { int f = forwardMostMove;
4309         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4310                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4311                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4312     }
4313     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4314     fprintf(debugFP, "moveNum = %d\n", moveNum);
4315     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4316     setbuf(debugFP, NULL);
4317   }
4318         if (moveNum <= backwardMostMove) {
4319             /* We don't know what the board looked like before
4320                this move.  Punt. */
4321           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4322             strcat(parseList[moveNum - 1], " ");
4323             strcat(parseList[moveNum - 1], elapsed_time);
4324             moveList[moveNum - 1][0] = NULLCHAR;
4325         } else if (strcmp(move_str, "none") == 0) {
4326             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4327             /* Again, we don't know what the board looked like;
4328                this is really the start of the game. */
4329             parseList[moveNum - 1][0] = NULLCHAR;
4330             moveList[moveNum - 1][0] = NULLCHAR;
4331             backwardMostMove = moveNum;
4332             startedFromSetupPosition = TRUE;
4333             fromX = fromY = toX = toY = -1;
4334         } else {
4335           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4336           //                 So we parse the long-algebraic move string in stead of the SAN move
4337           int valid; char buf[MSG_SIZ], *prom;
4338
4339           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4340                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4341           // str looks something like "Q/a1-a2"; kill the slash
4342           if(str[1] == '/')
4343             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4344           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4345           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4346                 strcat(buf, prom); // long move lacks promo specification!
4347           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4348                 if(appData.debugMode)
4349                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4350                 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4351           }
4352           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4353                                 &fromX, &fromY, &toX, &toY, &promoChar)
4354                || ParseOneMove(buf, moveNum - 1, &moveType,
4355                                 &fromX, &fromY, &toX, &toY, &promoChar);
4356           // end of long SAN patch
4357           if (valid) {
4358             (void) CoordsToAlgebraic(boards[moveNum - 1],
4359                                      PosFlags(moveNum - 1),
4360                                      fromY, fromX, toY, toX, promoChar,
4361                                      parseList[moveNum-1]);
4362             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4363               case MT_NONE:
4364               case MT_STALEMATE:
4365               default:
4366                 break;
4367               case MT_CHECK:
4368                 if(gameInfo.variant != VariantShogi)
4369                     strcat(parseList[moveNum - 1], "+");
4370                 break;
4371               case MT_CHECKMATE:
4372               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4373                 strcat(parseList[moveNum - 1], "#");
4374                 break;
4375             }
4376             strcat(parseList[moveNum - 1], " ");
4377             strcat(parseList[moveNum - 1], elapsed_time);
4378             /* currentMoveString is set as a side-effect of ParseOneMove */
4379             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '+';
4380             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4381             strcat(moveList[moveNum - 1], "\n");
4382
4383             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4384               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4385                 ChessSquare old, new = boards[moveNum][k][j];
4386                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4387                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4388                   if(old == new) continue;
4389                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4390                   else if(new == WhiteWazir || new == BlackWazir) {
4391                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4392                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4393                       else boards[moveNum][k][j] = old; // preserve type of Gold
4394                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4395                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4396               }
4397           } else {
4398             /* Move from ICS was illegal!?  Punt. */
4399             if (appData.debugMode) {
4400               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4401               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4402             }
4403             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4404             strcat(parseList[moveNum - 1], " ");
4405             strcat(parseList[moveNum - 1], elapsed_time);
4406             moveList[moveNum - 1][0] = NULLCHAR;
4407             fromX = fromY = toX = toY = -1;
4408           }
4409         }
4410   if (appData.debugMode) {
4411     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4412     setbuf(debugFP, NULL);
4413   }
4414
4415 #if ZIPPY
4416         /* Send move to chess program (BEFORE animating it). */
4417         if (appData.zippyPlay && !newGame && newMove &&
4418            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4419
4420             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4421                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4422                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4423                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4424                             move_str);
4425                     DisplayError(str, 0);
4426                 } else {
4427                     if (first.sendTime) {
4428                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4429                     }
4430                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4431                     if (firstMove && !bookHit) {
4432                         firstMove = FALSE;
4433                         if (first.useColors) {
4434                           SendToProgram(gameMode == IcsPlayingWhite ?
4435                                         "white\ngo\n" :
4436                                         "black\ngo\n", &first);
4437                         } else {
4438                           SendToProgram("go\n", &first);
4439                         }
4440                         first.maybeThinking = TRUE;
4441                     }
4442                 }
4443             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4444               if (moveList[moveNum - 1][0] == NULLCHAR) {
4445                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4446                 DisplayError(str, 0);
4447               } else {
4448                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4449                 SendMoveToProgram(moveNum - 1, &first);
4450               }
4451             }
4452         }
4453 #endif
4454     }
4455
4456     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4457         /* If move comes from a remote source, animate it.  If it
4458            isn't remote, it will have already been animated. */
4459         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4460             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4461         }
4462         if (!pausing && appData.highlightLastMove) {
4463             SetHighlights(fromX, fromY, toX, toY);
4464         }
4465     }
4466
4467     /* Start the clocks */
4468     whiteFlag = blackFlag = FALSE;
4469     appData.clockMode = !(basetime == 0 && increment == 0);
4470     if (ticking == 0) {
4471       ics_clock_paused = TRUE;
4472       StopClocks();
4473     } else if (ticking == 1) {
4474       ics_clock_paused = FALSE;
4475     }
4476     if (gameMode == IcsIdle ||
4477         relation == RELATION_OBSERVING_STATIC ||
4478         relation == RELATION_EXAMINING ||
4479         ics_clock_paused)
4480       DisplayBothClocks();
4481     else
4482       StartClocks();
4483
4484     /* Display opponents and material strengths */
4485     if (gameInfo.variant != VariantBughouse &&
4486         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4487         if (tinyLayout || smallLayout) {
4488             if(gameInfo.variant == VariantNormal)
4489               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4490                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4491                     basetime, increment);
4492             else
4493               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4494                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4495                     basetime, increment, (int) gameInfo.variant);
4496         } else {
4497             if(gameInfo.variant == VariantNormal)
4498               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4499                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4500                     basetime, increment);
4501             else
4502               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4503                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4504                     basetime, increment, VariantName(gameInfo.variant));
4505         }
4506         DisplayTitle(str);
4507   if (appData.debugMode) {
4508     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4509   }
4510     }
4511
4512
4513     /* Display the board */
4514     if (!pausing && !appData.noGUI) {
4515
4516       if (appData.premove)
4517           if (!gotPremove ||
4518              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4519              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4520               ClearPremoveHighlights();
4521
4522       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4523         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4524       DrawPosition(j, boards[currentMove]);
4525
4526       DisplayMove(moveNum - 1);
4527       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4528             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4529               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4530         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4531       }
4532     }
4533
4534     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4535 #if ZIPPY
4536     if(bookHit) { // [HGM] book: simulate book reply
4537         static char bookMove[MSG_SIZ]; // a bit generous?
4538
4539         programStats.nodes = programStats.depth = programStats.time =
4540         programStats.score = programStats.got_only_move = 0;
4541         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4542
4543         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4544         strcat(bookMove, bookHit);
4545         HandleMachineMove(bookMove, &first);
4546     }
4547 #endif
4548 }
4549
4550 void
4551 GetMoveListEvent()
4552 {
4553     char buf[MSG_SIZ];
4554     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4555         ics_getting_history = H_REQUESTED;
4556         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4557         SendToICS(buf);
4558     }
4559 }
4560
4561 void
4562 AnalysisPeriodicEvent(force)
4563      int force;
4564 {
4565     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4566          && !force) || !appData.periodicUpdates)
4567       return;
4568
4569     /* Send . command to Crafty to collect stats */
4570     SendToProgram(".\n", &first);
4571
4572     /* Don't send another until we get a response (this makes
4573        us stop sending to old Crafty's which don't understand
4574        the "." command (sending illegal cmds resets node count & time,
4575        which looks bad)) */
4576     programStats.ok_to_send = 0;
4577 }
4578
4579 void ics_update_width(new_width)
4580         int new_width;
4581 {
4582         ics_printf("set width %d\n", new_width);
4583 }
4584
4585 void
4586 SendMoveToProgram(moveNum, cps)
4587      int moveNum;
4588      ChessProgramState *cps;
4589 {
4590     char buf[MSG_SIZ];
4591
4592     if (cps->useUsermove) {
4593       SendToProgram("usermove ", cps);
4594     }
4595     if (cps->useSAN) {
4596       char *space;
4597       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4598         int len = space - parseList[moveNum];
4599         memcpy(buf, parseList[moveNum], len);
4600         buf[len++] = '\n';
4601         buf[len] = NULLCHAR;
4602       } else {
4603         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4604       }
4605       SendToProgram(buf, cps);
4606     } else {
4607       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4608         AlphaRank(moveList[moveNum], 4);
4609         SendToProgram(moveList[moveNum], cps);
4610         AlphaRank(moveList[moveNum], 4); // and back
4611       } else
4612       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4613        * the engine. It would be nice to have a better way to identify castle
4614        * moves here. */
4615       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4616                                                                          && cps->useOOCastle) {
4617         int fromX = moveList[moveNum][0] - AAA;
4618         int fromY = moveList[moveNum][1] - ONE;
4619         int toX = moveList[moveNum][2] - AAA;
4620         int toY = moveList[moveNum][3] - ONE;
4621         if((boards[moveNum][fromY][fromX] == WhiteKing
4622             && boards[moveNum][toY][toX] == WhiteRook)
4623            || (boards[moveNum][fromY][fromX] == BlackKing
4624                && boards[moveNum][toY][toX] == BlackRook)) {
4625           if(toX > fromX) SendToProgram("O-O\n", cps);
4626           else SendToProgram("O-O-O\n", cps);
4627         }
4628         else SendToProgram(moveList[moveNum], cps);
4629       }
4630       else SendToProgram(moveList[moveNum], cps);
4631       /* End of additions by Tord */
4632     }
4633
4634     /* [HGM] setting up the opening has brought engine in force mode! */
4635     /*       Send 'go' if we are in a mode where machine should play. */
4636     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4637         (gameMode == TwoMachinesPlay   ||
4638 #if ZIPPY
4639          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4640 #endif
4641          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4642         SendToProgram("go\n", cps);
4643   if (appData.debugMode) {
4644     fprintf(debugFP, "(extra)\n");
4645   }
4646     }
4647     setboardSpoiledMachineBlack = 0;
4648 }
4649
4650 void
4651 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4652      ChessMove moveType;
4653      int fromX, fromY, toX, toY;
4654      char promoChar;
4655 {
4656     char user_move[MSG_SIZ];
4657
4658     switch (moveType) {
4659       default:
4660         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4661                 (int)moveType, fromX, fromY, toX, toY);
4662         DisplayError(user_move + strlen("say "), 0);
4663         break;
4664       case WhiteKingSideCastle:
4665       case BlackKingSideCastle:
4666       case WhiteQueenSideCastleWild:
4667       case BlackQueenSideCastleWild:
4668       /* PUSH Fabien */
4669       case WhiteHSideCastleFR:
4670       case BlackHSideCastleFR:
4671       /* POP Fabien */
4672         snprintf(user_move, MSG_SIZ, "o-o\n");
4673         break;
4674       case WhiteQueenSideCastle:
4675       case BlackQueenSideCastle:
4676       case WhiteKingSideCastleWild:
4677       case BlackKingSideCastleWild:
4678       /* PUSH Fabien */
4679       case WhiteASideCastleFR:
4680       case BlackASideCastleFR:
4681       /* POP Fabien */
4682         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4683         break;
4684       case WhiteNonPromotion:
4685       case BlackNonPromotion:
4686         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4687         break;
4688       case WhitePromotion:
4689       case BlackPromotion:
4690         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4691           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4692                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4693                 PieceToChar(WhiteFerz));
4694         else if(gameInfo.variant == VariantGreat)
4695           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4696                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4697                 PieceToChar(WhiteMan));
4698         else
4699           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4700                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4701                 promoChar);
4702         break;
4703       case WhiteDrop:
4704       case BlackDrop:
4705       drop:
4706         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4707                  ToUpper(PieceToChar((ChessSquare) fromX)),
4708                  AAA + toX, ONE + toY);
4709         break;
4710       case IllegalMove:  /* could be a variant we don't quite understand */
4711         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4712       case NormalMove:
4713       case WhiteCapturesEnPassant:
4714       case BlackCapturesEnPassant:
4715         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4716                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4717         break;
4718     }
4719     SendToICS(user_move);
4720     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4721         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4722 }
4723
4724 void
4725 UploadGameEvent()
4726 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4727     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4728     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4729     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4730         DisplayError("You cannot do this while you are playing or observing", 0);
4731         return;
4732     }
4733     if(gameMode != IcsExamining) { // is this ever not the case?
4734         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4735
4736         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4737           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4738         } else { // on FICS we must first go to general examine mode
4739           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4740         }
4741         if(gameInfo.variant != VariantNormal) {
4742             // try figure out wild number, as xboard names are not always valid on ICS
4743             for(i=1; i<=36; i++) {
4744               snprintf(buf, MSG_SIZ, "wild/%d", i);
4745                 if(StringToVariant(buf) == gameInfo.variant) break;
4746             }
4747             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4748             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4749             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4750         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4751         SendToICS(ics_prefix);
4752         SendToICS(buf);
4753         if(startedFromSetupPosition || backwardMostMove != 0) {
4754           fen = PositionToFEN(backwardMostMove, NULL);
4755           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4756             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4757             SendToICS(buf);
4758           } else { // FICS: everything has to set by separate bsetup commands
4759             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4760             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4761             SendToICS(buf);
4762             if(!WhiteOnMove(backwardMostMove)) {
4763                 SendToICS("bsetup tomove black\n");
4764             }
4765             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4766             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4767             SendToICS(buf);
4768             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4769             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4770             SendToICS(buf);
4771             i = boards[backwardMostMove][EP_STATUS];
4772             if(i >= 0) { // set e.p.
4773               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4774                 SendToICS(buf);
4775             }
4776             bsetup++;
4777           }
4778         }
4779       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4780             SendToICS("bsetup done\n"); // switch to normal examining.
4781     }
4782     for(i = backwardMostMove; i<last; i++) {
4783         char buf[20];
4784         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4785         SendToICS(buf);
4786     }
4787     SendToICS(ics_prefix);
4788     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4789 }
4790
4791 void
4792 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4793      int rf, ff, rt, ft;
4794      char promoChar;
4795      char move[7];
4796 {
4797     if (rf == DROP_RANK) {
4798       sprintf(move, "%c@%c%c\n",
4799                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4800     } else {
4801         if (promoChar == 'x' || promoChar == NULLCHAR) {
4802           sprintf(move, "%c%c%c%c\n",
4803                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4804         } else {
4805             sprintf(move, "%c%c%c%c%c\n",
4806                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4807         }
4808     }
4809 }
4810
4811 void
4812 ProcessICSInitScript(f)
4813      FILE *f;
4814 {
4815     char buf[MSG_SIZ];
4816
4817     while (fgets(buf, MSG_SIZ, f)) {
4818         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4819     }
4820
4821     fclose(f);
4822 }
4823
4824
4825 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4826 void
4827 AlphaRank(char *move, int n)
4828 {
4829 //    char *p = move, c; int x, y;
4830
4831     if (appData.debugMode) {
4832         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4833     }
4834
4835     if(move[1]=='*' &&
4836        move[2]>='0' && move[2]<='9' &&
4837        move[3]>='a' && move[3]<='x'    ) {
4838         move[1] = '@';
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[0]>='0' && move[0]<='9' &&
4843        move[1]>='a' && move[1]<='x' &&
4844        move[2]>='0' && move[2]<='9' &&
4845        move[3]>='a' && move[3]<='x'    ) {
4846         /* input move, Shogi -> normal */
4847         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4848         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4849         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4850         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4851     } else
4852     if(move[1]=='@' &&
4853        move[3]>='0' && move[3]<='9' &&
4854        move[2]>='a' && move[2]<='x'    ) {
4855         move[1] = '*';
4856         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4857         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4858     } else
4859     if(
4860        move[0]>='a' && move[0]<='x' &&
4861        move[3]>='0' && move[3]<='9' &&
4862        move[2]>='a' && move[2]<='x'    ) {
4863          /* output move, normal -> Shogi */
4864         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4865         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4866         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4867         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4868         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4869     }
4870     if (appData.debugMode) {
4871         fprintf(debugFP, "   out = '%s'\n", move);
4872     }
4873 }
4874
4875 char yy_textstr[8000];
4876
4877 /* Parser for moves from gnuchess, ICS, or user typein box */
4878 Boolean
4879 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4880      char *move;
4881      int moveNum;
4882      ChessMove *moveType;
4883      int *fromX, *fromY, *toX, *toY;
4884      char *promoChar;
4885 {
4886     if (appData.debugMode) {
4887         fprintf(debugFP, "move to parse: %s\n", move);
4888     }
4889     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4890
4891     switch (*moveType) {
4892       case WhitePromotion:
4893       case BlackPromotion:
4894       case WhiteNonPromotion:
4895       case BlackNonPromotion:
4896       case NormalMove:
4897       case WhiteCapturesEnPassant:
4898       case BlackCapturesEnPassant:
4899       case WhiteKingSideCastle:
4900       case WhiteQueenSideCastle:
4901       case BlackKingSideCastle:
4902       case BlackQueenSideCastle:
4903       case WhiteKingSideCastleWild:
4904       case WhiteQueenSideCastleWild:
4905       case BlackKingSideCastleWild:
4906       case BlackQueenSideCastleWild:
4907       /* Code added by Tord: */
4908       case WhiteHSideCastleFR:
4909       case WhiteASideCastleFR:
4910       case BlackHSideCastleFR:
4911       case BlackASideCastleFR:
4912       /* End of code added by Tord */
4913       case IllegalMove:         /* bug or odd chess variant */
4914         *fromX = currentMoveString[0] - AAA;
4915         *fromY = currentMoveString[1] - ONE;
4916         *toX = currentMoveString[2] - AAA;
4917         *toY = currentMoveString[3] - ONE;
4918         *promoChar = currentMoveString[4];
4919         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4920             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4921     if (appData.debugMode) {
4922         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4923     }
4924             *fromX = *fromY = *toX = *toY = 0;
4925             return FALSE;
4926         }
4927         if (appData.testLegality) {
4928           return (*moveType != IllegalMove);
4929         } else {
4930           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4931                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4932         }
4933
4934       case WhiteDrop:
4935       case BlackDrop:
4936         *fromX = *moveType == WhiteDrop ?
4937           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4938           (int) CharToPiece(ToLower(currentMoveString[0]));
4939         *fromY = DROP_RANK;
4940         *toX = currentMoveString[2] - AAA;
4941         *toY = currentMoveString[3] - ONE;
4942         *promoChar = NULLCHAR;
4943         return TRUE;
4944
4945       case AmbiguousMove:
4946       case ImpossibleMove:
4947       case EndOfFile:
4948       case ElapsedTime:
4949       case Comment:
4950       case PGNTag:
4951       case NAG:
4952       case WhiteWins:
4953       case BlackWins:
4954       case GameIsDrawn:
4955       default:
4956     if (appData.debugMode) {
4957         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4958     }
4959         /* bug? */
4960         *fromX = *fromY = *toX = *toY = 0;
4961         *promoChar = NULLCHAR;
4962         return FALSE;
4963     }
4964 }
4965
4966
4967 void
4968 ParsePV(char *pv, Boolean storeComments)
4969 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4970   int fromX, fromY, toX, toY; char promoChar;
4971   ChessMove moveType;
4972   Boolean valid;
4973   int nr = 0;
4974
4975   endPV = forwardMostMove;
4976   do {
4977     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4978     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4979     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4980 if(appData.debugMode){
4981 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);
4982 }
4983     if(!valid && nr == 0 &&
4984        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4985         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4986         // Hande case where played move is different from leading PV move
4987         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4988         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4989         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4990         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4991           endPV += 2; // if position different, keep this
4992           moveList[endPV-1][0] = fromX + AAA;
4993           moveList[endPV-1][1] = fromY + ONE;
4994           moveList[endPV-1][2] = toX + AAA;
4995           moveList[endPV-1][3] = toY + ONE;
4996           parseList[endPV-1][0] = NULLCHAR;
4997           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4998         }
4999       }
5000     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5001     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5002     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5003     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5004         valid++; // allow comments in PV
5005         continue;
5006     }
5007     nr++;
5008     if(endPV+1 > framePtr) break; // no space, truncate
5009     if(!valid) break;
5010     endPV++;
5011     CopyBoard(boards[endPV], boards[endPV-1]);
5012     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5013     moveList[endPV-1][0] = fromX + AAA;
5014     moveList[endPV-1][1] = fromY + ONE;
5015     moveList[endPV-1][2] = toX + AAA;
5016     moveList[endPV-1][3] = toY + ONE;
5017     if(storeComments)
5018         CoordsToAlgebraic(boards[endPV - 1],
5019                              PosFlags(endPV - 1),
5020                              fromY, fromX, toY, toX, promoChar,
5021                              parseList[endPV - 1]);
5022     else
5023         parseList[endPV-1][0] = NULLCHAR;
5024   } while(valid);
5025   currentMove = endPV;
5026   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5027   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5028                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5029   DrawPosition(TRUE, boards[currentMove]);
5030 }
5031
5032 static int lastX, lastY;
5033
5034 Boolean
5035 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5036 {
5037         int startPV;
5038         char *p;
5039
5040         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5041         lastX = x; lastY = y;
5042         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5043         startPV = index;
5044         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5045         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5046         index = startPV;
5047         do{ while(buf[index] && buf[index] != '\n') index++;
5048         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5049         buf[index] = 0;
5050         ParsePV(buf+startPV, FALSE);
5051         *start = startPV; *end = index-1;
5052         return TRUE;
5053 }
5054
5055 Boolean
5056 LoadPV(int x, int y)
5057 { // called on right mouse click to load PV
5058   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5059   lastX = x; lastY = y;
5060   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5061   return TRUE;
5062 }
5063
5064 void
5065 UnLoadPV()
5066 {
5067   if(endPV < 0) return;
5068   endPV = -1;
5069   currentMove = forwardMostMove;
5070   ClearPremoveHighlights();
5071   DrawPosition(TRUE, boards[currentMove]);
5072 }
5073
5074 void
5075 MovePV(int x, int y, int h)
5076 { // step through PV based on mouse coordinates (called on mouse move)
5077   int margin = h>>3, step = 0;
5078
5079   if(endPV < 0) return;
5080   // we must somehow check if right button is still down (might be released off board!)
5081   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5082   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5083   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5084   if(!step) return;
5085   lastX = x; lastY = y;
5086   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5087   currentMove += step;
5088   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5089   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5090                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5091   DrawPosition(FALSE, boards[currentMove]);
5092 }
5093
5094
5095 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5096 // All positions will have equal probability, but the current method will not provide a unique
5097 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5098 #define DARK 1
5099 #define LITE 2
5100 #define ANY 3
5101
5102 int squaresLeft[4];
5103 int piecesLeft[(int)BlackPawn];
5104 int seed, nrOfShuffles;
5105
5106 void GetPositionNumber()
5107 {       // sets global variable seed
5108         int i;
5109
5110         seed = appData.defaultFrcPosition;
5111         if(seed < 0) { // randomize based on time for negative FRC position numbers
5112                 for(i=0; i<50; i++) seed += random();
5113                 seed = random() ^ random() >> 8 ^ random() << 8;
5114                 if(seed<0) seed = -seed;
5115         }
5116 }
5117
5118 int put(Board board, int pieceType, int rank, int n, int shade)
5119 // put the piece on the (n-1)-th empty squares of the given shade
5120 {
5121         int i;
5122
5123         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5124                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5125                         board[rank][i] = (ChessSquare) pieceType;
5126                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5127                         squaresLeft[ANY]--;
5128                         piecesLeft[pieceType]--;
5129                         return i;
5130                 }
5131         }
5132         return -1;
5133 }
5134
5135
5136 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5137 // calculate where the next piece goes, (any empty square), and put it there
5138 {
5139         int i;
5140
5141         i = seed % squaresLeft[shade];
5142         nrOfShuffles *= squaresLeft[shade];
5143         seed /= squaresLeft[shade];
5144         put(board, pieceType, rank, i, shade);
5145 }
5146
5147 void AddTwoPieces(Board board, int pieceType, int rank)
5148 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5149 {
5150         int i, n=squaresLeft[ANY], j=n-1, k;
5151
5152         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5153         i = seed % k;  // pick one
5154         nrOfShuffles *= k;
5155         seed /= k;
5156         while(i >= j) i -= j--;
5157         j = n - 1 - j; i += j;
5158         put(board, pieceType, rank, j, ANY);
5159         put(board, pieceType, rank, i, ANY);
5160 }
5161
5162 void SetUpShuffle(Board board, int number)
5163 {
5164         int i, p, first=1;
5165
5166         GetPositionNumber(); nrOfShuffles = 1;
5167
5168         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5169         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5170         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5171
5172         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5173
5174         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5175             p = (int) board[0][i];
5176             if(p < (int) BlackPawn) piecesLeft[p] ++;
5177             board[0][i] = EmptySquare;
5178         }
5179
5180         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5181             // shuffles restricted to allow normal castling put KRR first
5182             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5183                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5184             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5185                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5186             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5187                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5188             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5189                 put(board, WhiteRook, 0, 0, ANY);
5190             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5191         }
5192
5193         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5194             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5195             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5196                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5197                 while(piecesLeft[p] >= 2) {
5198                     AddOnePiece(board, p, 0, LITE);
5199                     AddOnePiece(board, p, 0, DARK);
5200                 }
5201                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5202             }
5203
5204         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5205             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5206             // but we leave King and Rooks for last, to possibly obey FRC restriction
5207             if(p == (int)WhiteRook) continue;
5208             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5209             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5210         }
5211
5212         // now everything is placed, except perhaps King (Unicorn) and Rooks
5213
5214         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5215             // Last King gets castling rights
5216             while(piecesLeft[(int)WhiteUnicorn]) {
5217                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5218                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5219             }
5220
5221             while(piecesLeft[(int)WhiteKing]) {
5222                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5223                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5224             }
5225
5226
5227         } else {
5228             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5229             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5230         }
5231
5232         // Only Rooks can be left; simply place them all
5233         while(piecesLeft[(int)WhiteRook]) {
5234                 i = put(board, WhiteRook, 0, 0, ANY);
5235                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5236                         if(first) {
5237                                 first=0;
5238                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5239                         }
5240                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5241                 }
5242         }
5243         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5244             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5245         }
5246
5247         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5248 }
5249
5250 int SetCharTable( char *table, const char * map )
5251 /* [HGM] moved here from winboard.c because of its general usefulness */
5252 /*       Basically a safe strcpy that uses the last character as King */
5253 {
5254     int result = FALSE; int NrPieces;
5255
5256     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5257                     && NrPieces >= 12 && !(NrPieces&1)) {
5258         int i; /* [HGM] Accept even length from 12 to 34 */
5259
5260         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5261         for( i=0; i<NrPieces/2-1; i++ ) {
5262             table[i] = map[i];
5263             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5264         }
5265         table[(int) WhiteKing]  = map[NrPieces/2-1];
5266         table[(int) BlackKing]  = map[NrPieces-1];
5267
5268         result = TRUE;
5269     }
5270
5271     return result;
5272 }
5273
5274 void Prelude(Board board)
5275 {       // [HGM] superchess: random selection of exo-pieces
5276         int i, j, k; ChessSquare p;
5277         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5278
5279         GetPositionNumber(); // use FRC position number
5280
5281         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5282             SetCharTable(pieceToChar, appData.pieceToCharTable);
5283             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5284                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5285         }
5286
5287         j = seed%4;                 seed /= 4;
5288         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5289         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5290         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5291         j = seed%3 + (seed%3 >= j); seed /= 3;
5292         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5293         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5294         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5295         j = seed%3;                 seed /= 3;
5296         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5297         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5298         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5299         j = seed%2 + (seed%2 >= j); seed /= 2;
5300         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5301         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5302         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5303         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5304         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5305         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5306         put(board, exoPieces[0],    0, 0, ANY);
5307         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5308 }
5309
5310 void
5311 InitPosition(redraw)
5312      int redraw;
5313 {
5314     ChessSquare (* pieces)[BOARD_FILES];
5315     int i, j, pawnRow, overrule,
5316     oldx = gameInfo.boardWidth,
5317     oldy = gameInfo.boardHeight,
5318     oldh = gameInfo.holdingsWidth,
5319     oldv = gameInfo.variant;
5320
5321     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5322
5323     /* [AS] Initialize pv info list [HGM] and game status */
5324     {
5325         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5326             pvInfoList[i].depth = 0;
5327             boards[i][EP_STATUS] = EP_NONE;
5328             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5329         }
5330
5331         initialRulePlies = 0; /* 50-move counter start */
5332
5333         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5334         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5335     }
5336
5337
5338     /* [HGM] logic here is completely changed. In stead of full positions */
5339     /* the initialized data only consist of the two backranks. The switch */
5340     /* selects which one we will use, which is than copied to the Board   */
5341     /* initialPosition, which for the rest is initialized by Pawns and    */
5342     /* empty squares. This initial position is then copied to boards[0],  */
5343     /* possibly after shuffling, so that it remains available.            */
5344
5345     gameInfo.holdingsWidth = 0; /* default board sizes */
5346     gameInfo.boardWidth    = 8;
5347     gameInfo.boardHeight   = 8;
5348     gameInfo.holdingsSize  = 0;
5349     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5350     for(i=0; i<BOARD_FILES-2; i++)
5351       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5352     initialPosition[EP_STATUS] = EP_NONE;
5353     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5354     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5355          SetCharTable(pieceNickName, appData.pieceNickNames);
5356     else SetCharTable(pieceNickName, "............");
5357
5358     switch (gameInfo.variant) {
5359     case VariantFischeRandom:
5360       shuffleOpenings = TRUE;
5361     default:
5362       pieces = FIDEArray;
5363       break;
5364     case VariantShatranj:
5365       pieces = ShatranjArray;
5366       nrCastlingRights = 0;
5367       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5368       break;
5369     case VariantMakruk:
5370       pieces = makrukArray;
5371       nrCastlingRights = 0;
5372       startedFromSetupPosition = TRUE;
5373       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5374       break;
5375     case VariantTwoKings:
5376       pieces = twoKingsArray;
5377       break;
5378     case VariantCapaRandom:
5379       shuffleOpenings = TRUE;
5380     case VariantCapablanca:
5381       pieces = CapablancaArray;
5382       gameInfo.boardWidth = 10;
5383       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5384       break;
5385     case VariantGothic:
5386       pieces = GothicArray;
5387       gameInfo.boardWidth = 10;
5388       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5389       break;
5390     case VariantJanus:
5391       pieces = JanusArray;
5392       gameInfo.boardWidth = 10;
5393       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5394       nrCastlingRights = 6;
5395         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5396         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5397         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5398         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5399         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5400         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5401       break;
5402     case VariantFalcon:
5403       pieces = FalconArray;
5404       gameInfo.boardWidth = 10;
5405       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5406       break;
5407     case VariantXiangqi:
5408       pieces = XiangqiArray;
5409       gameInfo.boardWidth  = 9;
5410       gameInfo.boardHeight = 10;
5411       nrCastlingRights = 0;
5412       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5413       break;
5414     case VariantShogi:
5415       pieces = ShogiArray;
5416       gameInfo.boardWidth  = 9;
5417       gameInfo.boardHeight = 9;
5418       gameInfo.holdingsSize = 7;
5419       nrCastlingRights = 0;
5420       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5421       break;
5422     case VariantCourier:
5423       pieces = CourierArray;
5424       gameInfo.boardWidth  = 12;
5425       nrCastlingRights = 0;
5426       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5427       break;
5428     case VariantKnightmate:
5429       pieces = KnightmateArray;
5430       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5431       break;
5432     case VariantFairy:
5433       pieces = fairyArray;
5434       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5435       break;
5436     case VariantGreat:
5437       pieces = GreatArray;
5438       gameInfo.boardWidth = 10;
5439       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5440       gameInfo.holdingsSize = 8;
5441       break;
5442     case VariantSuper:
5443       pieces = FIDEArray;
5444       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5445       gameInfo.holdingsSize = 8;
5446       startedFromSetupPosition = TRUE;
5447       break;
5448     case VariantCrazyhouse:
5449     case VariantBughouse:
5450       pieces = FIDEArray;
5451       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5452       gameInfo.holdingsSize = 5;
5453       break;
5454     case VariantWildCastle:
5455       pieces = FIDEArray;
5456       /* !!?shuffle with kings guaranteed to be on d or e file */
5457       shuffleOpenings = 1;
5458       break;
5459     case VariantNoCastle:
5460       pieces = FIDEArray;
5461       nrCastlingRights = 0;
5462       /* !!?unconstrained back-rank shuffle */
5463       shuffleOpenings = 1;
5464       break;
5465     }
5466
5467     overrule = 0;
5468     if(appData.NrFiles >= 0) {
5469         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5470         gameInfo.boardWidth = appData.NrFiles;
5471     }
5472     if(appData.NrRanks >= 0) {
5473         gameInfo.boardHeight = appData.NrRanks;
5474     }
5475     if(appData.holdingsSize >= 0) {
5476         i = appData.holdingsSize;
5477         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5478         gameInfo.holdingsSize = i;
5479     }
5480     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5481     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5482         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5483
5484     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5485     if(pawnRow < 1) pawnRow = 1;
5486     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5487
5488     /* User pieceToChar list overrules defaults */
5489     if(appData.pieceToCharTable != NULL)
5490         SetCharTable(pieceToChar, appData.pieceToCharTable);
5491
5492     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5493
5494         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5495             s = (ChessSquare) 0; /* account holding counts in guard band */
5496         for( i=0; i<BOARD_HEIGHT; i++ )
5497             initialPosition[i][j] = s;
5498
5499         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5500         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5501         initialPosition[pawnRow][j] = WhitePawn;
5502         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5503         if(gameInfo.variant == VariantXiangqi) {
5504             if(j&1) {
5505                 initialPosition[pawnRow][j] =
5506                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5507                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5508                    initialPosition[2][j] = WhiteCannon;
5509                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5510                 }
5511             }
5512         }
5513         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5514     }
5515     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5516
5517             j=BOARD_LEFT+1;
5518             initialPosition[1][j] = WhiteBishop;
5519             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5520             j=BOARD_RGHT-2;
5521             initialPosition[1][j] = WhiteRook;
5522             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5523     }
5524
5525     if( nrCastlingRights == -1) {
5526         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5527         /*       This sets default castling rights from none to normal corners   */
5528         /* Variants with other castling rights must set them themselves above    */
5529         nrCastlingRights = 6;
5530
5531         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5532         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5533         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5534         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5535         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5536         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5537      }
5538
5539      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5540      if(gameInfo.variant == VariantGreat) { // promotion commoners
5541         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5542         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5543         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5544         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5545      }
5546   if (appData.debugMode) {
5547     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5548   }
5549     if(shuffleOpenings) {
5550         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5551         startedFromSetupPosition = TRUE;
5552     }
5553     if(startedFromPositionFile) {
5554       /* [HGM] loadPos: use PositionFile for every new game */
5555       CopyBoard(initialPosition, filePosition);
5556       for(i=0; i<nrCastlingRights; i++)
5557           initialRights[i] = filePosition[CASTLING][i];
5558       startedFromSetupPosition = TRUE;
5559     }
5560
5561     CopyBoard(boards[0], initialPosition);
5562
5563     if(oldx != gameInfo.boardWidth ||
5564        oldy != gameInfo.boardHeight ||
5565        oldh != gameInfo.holdingsWidth
5566 #ifdef GOTHIC
5567        || oldv == VariantGothic ||        // For licensing popups
5568        gameInfo.variant == VariantGothic
5569 #endif
5570 #ifdef FALCON
5571        || oldv == VariantFalcon ||
5572        gameInfo.variant == VariantFalcon
5573 #endif
5574                                          )
5575             InitDrawingSizes(-2 ,0);
5576
5577     if (redraw)
5578       DrawPosition(TRUE, boards[currentMove]);
5579 }
5580
5581 void
5582 SendBoard(cps, moveNum)
5583      ChessProgramState *cps;
5584      int moveNum;
5585 {
5586     char message[MSG_SIZ];
5587
5588     if (cps->useSetboard) {
5589       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5590       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5591       SendToProgram(message, cps);
5592       free(fen);
5593
5594     } else {
5595       ChessSquare *bp;
5596       int i, j;
5597       /* Kludge to set black to move, avoiding the troublesome and now
5598        * deprecated "black" command.
5599        */
5600       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\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     if(autoQueen) { // predetermined
5724         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5725              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5726         else *promoChoice = PieceToChar(BlackQueen);
5727         return FALSE;
5728     }
5729
5730     // suppress promotion popup on illegal moves that are not premoves
5731     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5732               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5733     if(appData.testLegality && !premove) {
5734         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5735                         fromY, fromX, toY, toX, NULLCHAR);
5736         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5737             return FALSE;
5738     }
5739
5740     return TRUE;
5741 }
5742
5743 int
5744 InPalace(row, column)
5745      int row, column;
5746 {   /* [HGM] for Xiangqi */
5747     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5748          column < (BOARD_WIDTH + 4)/2 &&
5749          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5750     return FALSE;
5751 }
5752
5753 int
5754 PieceForSquare (x, y)
5755      int x;
5756      int y;
5757 {
5758   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5759      return -1;
5760   else
5761      return boards[currentMove][y][x];
5762 }
5763
5764 int
5765 OKToStartUserMove(x, y)
5766      int x, y;
5767 {
5768     ChessSquare from_piece;
5769     int white_piece;
5770
5771     if (matchMode) return FALSE;
5772     if (gameMode == EditPosition) return TRUE;
5773
5774     if (x >= 0 && y >= 0)
5775       from_piece = boards[currentMove][y][x];
5776     else
5777       from_piece = EmptySquare;
5778
5779     if (from_piece == EmptySquare) return FALSE;
5780
5781     white_piece = (int)from_piece >= (int)WhitePawn &&
5782       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5783
5784     switch (gameMode) {
5785       case PlayFromGameFile:
5786       case AnalyzeFile:
5787       case TwoMachinesPlay:
5788       case EndOfGame:
5789         return FALSE;
5790
5791       case IcsObserving:
5792       case IcsIdle:
5793         return FALSE;
5794
5795       case MachinePlaysWhite:
5796       case IcsPlayingBlack:
5797         if (appData.zippyPlay) return FALSE;
5798         if (white_piece) {
5799             DisplayMoveError(_("You are playing Black"));
5800             return FALSE;
5801         }
5802         break;
5803
5804       case MachinePlaysBlack:
5805       case IcsPlayingWhite:
5806         if (appData.zippyPlay) return FALSE;
5807         if (!white_piece) {
5808             DisplayMoveError(_("You are playing White"));
5809             return FALSE;
5810         }
5811         break;
5812
5813       case EditGame:
5814         if (!white_piece && WhiteOnMove(currentMove)) {
5815             DisplayMoveError(_("It is White's turn"));
5816             return FALSE;
5817         }
5818         if (white_piece && !WhiteOnMove(currentMove)) {
5819             DisplayMoveError(_("It is Black's turn"));
5820             return FALSE;
5821         }
5822         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5823             /* Editing correspondence game history */
5824             /* Could disallow this or prompt for confirmation */
5825             cmailOldMove = -1;
5826         }
5827         break;
5828
5829       case BeginningOfGame:
5830         if (appData.icsActive) return FALSE;
5831         if (!appData.noChessProgram) {
5832             if (!white_piece) {
5833                 DisplayMoveError(_("You are playing White"));
5834                 return FALSE;
5835             }
5836         }
5837         break;
5838
5839       case Training:
5840         if (!white_piece && WhiteOnMove(currentMove)) {
5841             DisplayMoveError(_("It is White's turn"));
5842             return FALSE;
5843         }
5844         if (white_piece && !WhiteOnMove(currentMove)) {
5845             DisplayMoveError(_("It is Black's turn"));
5846             return FALSE;
5847         }
5848         break;
5849
5850       default:
5851       case IcsExamining:
5852         break;
5853     }
5854     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5855         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5856         && gameMode != AnalyzeFile && gameMode != Training) {
5857         DisplayMoveError(_("Displayed position is not current"));
5858         return FALSE;
5859     }
5860     return TRUE;
5861 }
5862
5863 Boolean
5864 OnlyMove(int *x, int *y, Boolean captures) {
5865     DisambiguateClosure cl;
5866     if (appData.zippyPlay) return FALSE;
5867     switch(gameMode) {
5868       case MachinePlaysBlack:
5869       case IcsPlayingWhite:
5870       case BeginningOfGame:
5871         if(!WhiteOnMove(currentMove)) return FALSE;
5872         break;
5873       case MachinePlaysWhite:
5874       case IcsPlayingBlack:
5875         if(WhiteOnMove(currentMove)) return FALSE;
5876         break;
5877       default:
5878         return FALSE;
5879     }
5880     cl.pieceIn = EmptySquare;
5881     cl.rfIn = *y;
5882     cl.ffIn = *x;
5883     cl.rtIn = -1;
5884     cl.ftIn = -1;
5885     cl.promoCharIn = NULLCHAR;
5886     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5887     if( cl.kind == NormalMove ||
5888         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5889         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5890         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5891       fromX = cl.ff;
5892       fromY = cl.rf;
5893       *x = cl.ft;
5894       *y = cl.rt;
5895       return TRUE;
5896     }
5897     if(cl.kind != ImpossibleMove) return FALSE;
5898     cl.pieceIn = EmptySquare;
5899     cl.rfIn = -1;
5900     cl.ffIn = -1;
5901     cl.rtIn = *y;
5902     cl.ftIn = *x;
5903     cl.promoCharIn = NULLCHAR;
5904     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5905     if( cl.kind == NormalMove ||
5906         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5907         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5908         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5909       fromX = cl.ff;
5910       fromY = cl.rf;
5911       *x = cl.ft;
5912       *y = cl.rt;
5913       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5914       return TRUE;
5915     }
5916     return FALSE;
5917 }
5918
5919 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5920 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5921 int lastLoadGameUseList = FALSE;
5922 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5923 ChessMove lastLoadGameStart = EndOfFile;
5924
5925 void
5926 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5927      int fromX, fromY, toX, toY;
5928      int promoChar;
5929 {
5930     ChessMove moveType;
5931     ChessSquare pdown, pup;
5932
5933     /* Check if the user is playing in turn.  This is complicated because we
5934        let the user "pick up" a piece before it is his turn.  So the piece he
5935        tried to pick up may have been captured by the time he puts it down!
5936        Therefore we use the color the user is supposed to be playing in this
5937        test, not the color of the piece that is currently on the starting
5938        square---except in EditGame mode, where the user is playing both
5939        sides; fortunately there the capture race can't happen.  (It can
5940        now happen in IcsExamining mode, but that's just too bad.  The user
5941        will get a somewhat confusing message in that case.)
5942        */
5943
5944     switch (gameMode) {
5945       case PlayFromGameFile:
5946       case AnalyzeFile:
5947       case TwoMachinesPlay:
5948       case EndOfGame:
5949       case IcsObserving:
5950       case IcsIdle:
5951         /* We switched into a game mode where moves are not accepted,
5952            perhaps while the mouse button was down. */
5953         return;
5954
5955       case MachinePlaysWhite:
5956         /* User is moving for Black */
5957         if (WhiteOnMove(currentMove)) {
5958             DisplayMoveError(_("It is White's turn"));
5959             return;
5960         }
5961         break;
5962
5963       case MachinePlaysBlack:
5964         /* User is moving for White */
5965         if (!WhiteOnMove(currentMove)) {
5966             DisplayMoveError(_("It is Black's turn"));
5967             return;
5968         }
5969         break;
5970
5971       case EditGame:
5972       case IcsExamining:
5973       case BeginningOfGame:
5974       case AnalyzeMode:
5975       case Training:
5976         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5977             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5978             /* User is moving for Black */
5979             if (WhiteOnMove(currentMove)) {
5980                 DisplayMoveError(_("It is White's turn"));
5981                 return;
5982             }
5983         } else {
5984             /* User is moving for White */
5985             if (!WhiteOnMove(currentMove)) {
5986                 DisplayMoveError(_("It is Black's turn"));
5987                 return;
5988             }
5989         }
5990         break;
5991
5992       case IcsPlayingBlack:
5993         /* User is moving for Black */
5994         if (WhiteOnMove(currentMove)) {
5995             if (!appData.premove) {
5996                 DisplayMoveError(_("It is White's turn"));
5997             } else if (toX >= 0 && toY >= 0) {
5998                 premoveToX = toX;
5999                 premoveToY = toY;
6000                 premoveFromX = fromX;
6001                 premoveFromY = fromY;
6002                 premovePromoChar = promoChar;
6003                 gotPremove = 1;
6004                 if (appData.debugMode)
6005                     fprintf(debugFP, "Got premove: fromX %d,"
6006                             "fromY %d, toX %d, toY %d\n",
6007                             fromX, fromY, toX, toY);
6008             }
6009             return;
6010         }
6011         break;
6012
6013       case IcsPlayingWhite:
6014         /* User is moving for White */
6015         if (!WhiteOnMove(currentMove)) {
6016             if (!appData.premove) {
6017                 DisplayMoveError(_("It is Black's turn"));
6018             } else if (toX >= 0 && toY >= 0) {
6019                 premoveToX = toX;
6020                 premoveToY = toY;
6021                 premoveFromX = fromX;
6022                 premoveFromY = fromY;
6023                 premovePromoChar = promoChar;
6024                 gotPremove = 1;
6025                 if (appData.debugMode)
6026                     fprintf(debugFP, "Got premove: fromX %d,"
6027                             "fromY %d, toX %d, toY %d\n",
6028                             fromX, fromY, toX, toY);
6029             }
6030             return;
6031         }
6032         break;
6033
6034       default:
6035         break;
6036
6037       case EditPosition:
6038         /* EditPosition, empty square, or different color piece;
6039            click-click move is possible */
6040         if (toX == -2 || toY == -2) {
6041             boards[0][fromY][fromX] = EmptySquare;
6042             DrawPosition(FALSE, boards[currentMove]);
6043             return;
6044         } else if (toX >= 0 && toY >= 0) {
6045             boards[0][toY][toX] = boards[0][fromY][fromX];
6046             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6047                 if(boards[0][fromY][0] != EmptySquare) {
6048                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6049                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6050                 }
6051             } else
6052             if(fromX == BOARD_RGHT+1) {
6053                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6054                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6055                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6056                 }
6057             } else
6058             boards[0][fromY][fromX] = EmptySquare;
6059             DrawPosition(FALSE, boards[currentMove]);
6060             return;
6061         }
6062         return;
6063     }
6064
6065     if(toX < 0 || toY < 0) return;
6066     pdown = boards[currentMove][fromY][fromX];
6067     pup = boards[currentMove][toY][toX];
6068
6069     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6070     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
6071          if( pup != EmptySquare ) return;
6072          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6073            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6074                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6075            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6076            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6077            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6078            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6079          fromY = DROP_RANK;
6080     }
6081
6082     /* [HGM] always test for legality, to get promotion info */
6083     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6084                                          fromY, fromX, toY, toX, promoChar);
6085     /* [HGM] but possibly ignore an IllegalMove result */
6086     if (appData.testLegality) {
6087         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6088             DisplayMoveError(_("Illegal move"));
6089             return;
6090         }
6091     }
6092
6093     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6094 }
6095
6096 /* Common tail of UserMoveEvent and DropMenuEvent */
6097 int
6098 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6099      ChessMove moveType;
6100      int fromX, fromY, toX, toY;
6101      /*char*/int promoChar;
6102 {
6103     char *bookHit = 0;
6104
6105     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6106         // [HGM] superchess: suppress promotions to non-available piece
6107         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6108         if(WhiteOnMove(currentMove)) {
6109             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6110         } else {
6111             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6112         }
6113     }
6114
6115     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6116        move type in caller when we know the move is a legal promotion */
6117     if(moveType == NormalMove && promoChar)
6118         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6119
6120     /* [HGM] <popupFix> The following if has been moved here from
6121        UserMoveEvent(). Because it seemed to belong here (why not allow
6122        piece drops in training games?), and because it can only be
6123        performed after it is known to what we promote. */
6124     if (gameMode == Training) {
6125       /* compare the move played on the board to the next move in the
6126        * game. If they match, display the move and the opponent's response.
6127        * If they don't match, display an error message.
6128        */
6129       int saveAnimate;
6130       Board testBoard;
6131       CopyBoard(testBoard, boards[currentMove]);
6132       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6133
6134       if (CompareBoards(testBoard, boards[currentMove+1])) {
6135         ForwardInner(currentMove+1);
6136
6137         /* Autoplay the opponent's response.
6138          * if appData.animate was TRUE when Training mode was entered,
6139          * the response will be animated.
6140          */
6141         saveAnimate = appData.animate;
6142         appData.animate = animateTraining;
6143         ForwardInner(currentMove+1);
6144         appData.animate = saveAnimate;
6145
6146         /* check for the end of the game */
6147         if (currentMove >= forwardMostMove) {
6148           gameMode = PlayFromGameFile;
6149           ModeHighlight();
6150           SetTrainingModeOff();
6151           DisplayInformation(_("End of game"));
6152         }
6153       } else {
6154         DisplayError(_("Incorrect move"), 0);
6155       }
6156       return 1;
6157     }
6158
6159   /* Ok, now we know that the move is good, so we can kill
6160      the previous line in Analysis Mode */
6161   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6162                                 && currentMove < forwardMostMove) {
6163     if(appData.variations) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6164     else forwardMostMove = currentMove;
6165   }
6166
6167   /* If we need the chess program but it's dead, restart it */
6168   ResurrectChessProgram();
6169
6170   /* A user move restarts a paused game*/
6171   if (pausing)
6172     PauseEvent();
6173
6174   thinkOutput[0] = NULLCHAR;
6175
6176   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6177
6178   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6179
6180   if (gameMode == BeginningOfGame) {
6181     if (appData.noChessProgram) {
6182       gameMode = EditGame;
6183       SetGameInfo();
6184     } else {
6185       char buf[MSG_SIZ];
6186       gameMode = MachinePlaysBlack;
6187       StartClocks();
6188       SetGameInfo();
6189       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6190       DisplayTitle(buf);
6191       if (first.sendName) {
6192         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6193         SendToProgram(buf, &first);
6194       }
6195       StartClocks();
6196     }
6197     ModeHighlight();
6198   }
6199
6200   /* Relay move to ICS or chess engine */
6201   if (appData.icsActive) {
6202     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6203         gameMode == IcsExamining) {
6204       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6205         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6206         SendToICS("draw ");
6207         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6208       }
6209       // also send plain move, in case ICS does not understand atomic claims
6210       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6211       ics_user_moved = 1;
6212     }
6213   } else {
6214     if (first.sendTime && (gameMode == BeginningOfGame ||
6215                            gameMode == MachinePlaysWhite ||
6216                            gameMode == MachinePlaysBlack)) {
6217       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6218     }
6219     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6220          // [HGM] book: if program might be playing, let it use book
6221         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6222         first.maybeThinking = TRUE;
6223     } else SendMoveToProgram(forwardMostMove-1, &first);
6224     if (currentMove == cmailOldMove + 1) {
6225       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6226     }
6227   }
6228
6229   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6230
6231   switch (gameMode) {
6232   case EditGame:
6233     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6234     case MT_NONE:
6235     case MT_CHECK:
6236       break;
6237     case MT_CHECKMATE:
6238     case MT_STAINMATE:
6239       if (WhiteOnMove(currentMove)) {
6240         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6241       } else {
6242         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6243       }
6244       break;
6245     case MT_STALEMATE:
6246       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6247       break;
6248     }
6249     break;
6250
6251   case MachinePlaysBlack:
6252   case MachinePlaysWhite:
6253     /* disable certain menu options while machine is thinking */
6254     SetMachineThinkingEnables();
6255     break;
6256
6257   default:
6258     break;
6259   }
6260
6261   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6262
6263   if(bookHit) { // [HGM] book: simulate book reply
6264         static char bookMove[MSG_SIZ]; // a bit generous?
6265
6266         programStats.nodes = programStats.depth = programStats.time =
6267         programStats.score = programStats.got_only_move = 0;
6268         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6269
6270         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6271         strcat(bookMove, bookHit);
6272         HandleMachineMove(bookMove, &first);
6273   }
6274   return 1;
6275 }
6276
6277 void
6278 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6279      Board board;
6280      int flags;
6281      ChessMove kind;
6282      int rf, ff, rt, ft;
6283      VOIDSTAR closure;
6284 {
6285     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6286     Markers *m = (Markers *) closure;
6287     if(rf == fromY && ff == fromX)
6288         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6289                          || kind == WhiteCapturesEnPassant
6290                          || kind == BlackCapturesEnPassant);
6291     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6292 }
6293
6294 void
6295 MarkTargetSquares(int clear)
6296 {
6297   int x, y;
6298   if(!appData.markers || !appData.highlightDragging ||
6299      !appData.testLegality || gameMode == EditPosition) return;
6300   if(clear) {
6301     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6302   } else {
6303     int capt = 0;
6304     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6305     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6306       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6307       if(capt)
6308       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6309     }
6310   }
6311   DrawPosition(TRUE, NULL);
6312 }
6313
6314 void LeftClick(ClickType clickType, int xPix, int yPix)
6315 {
6316     int x, y;
6317     Boolean saveAnimate;
6318     static int second = 0, promotionChoice = 0, dragging = 0;
6319     char promoChoice = NULLCHAR;
6320
6321     if(appData.seekGraph && appData.icsActive && loggedOn &&
6322         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6323         SeekGraphClick(clickType, xPix, yPix, 0);
6324         return;
6325     }
6326
6327     if (clickType == Press) ErrorPopDown();
6328     MarkTargetSquares(1);
6329
6330     x = EventToSquare(xPix, BOARD_WIDTH);
6331     y = EventToSquare(yPix, BOARD_HEIGHT);
6332     if (!flipView && y >= 0) {
6333         y = BOARD_HEIGHT - 1 - y;
6334     }
6335     if (flipView && x >= 0) {
6336         x = BOARD_WIDTH - 1 - x;
6337     }
6338
6339     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6340         if(clickType == Release) return; // ignore upclick of click-click destination
6341         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6342         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6343         if(gameInfo.holdingsWidth &&
6344                 (WhiteOnMove(currentMove)
6345                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6346                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6347             // click in right holdings, for determining promotion piece
6348             ChessSquare p = boards[currentMove][y][x];
6349             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6350             if(p != EmptySquare) {
6351                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6352                 fromX = fromY = -1;
6353                 return;
6354             }
6355         }
6356         DrawPosition(FALSE, boards[currentMove]);
6357         return;
6358     }
6359
6360     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6361     if(clickType == Press
6362             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6363               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6364               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6365         return;
6366
6367     autoQueen = appData.alwaysPromoteToQueen;
6368
6369     if (fromX == -1) {
6370       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6371         if (clickType == Press) {
6372             /* First square */
6373             if (OKToStartUserMove(x, y)) {
6374                 fromX = x;
6375                 fromY = y;
6376                 second = 0;
6377                 MarkTargetSquares(0);
6378                 DragPieceBegin(xPix, yPix); dragging = 1;
6379                 if (appData.highlightDragging) {
6380                     SetHighlights(x, y, -1, -1);
6381                 }
6382             }
6383         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6384             DragPieceEnd(xPix, yPix); dragging = 0;
6385             DrawPosition(FALSE, NULL);
6386         }
6387         return;
6388       }
6389     }
6390
6391     /* fromX != -1 */
6392     if (clickType == Press && gameMode != EditPosition) {
6393         ChessSquare fromP;
6394         ChessSquare toP;
6395         int frc;
6396
6397         // ignore off-board to clicks
6398         if(y < 0 || x < 0) return;
6399
6400         /* Check if clicking again on the same color piece */
6401         fromP = boards[currentMove][fromY][fromX];
6402         toP = boards[currentMove][y][x];
6403         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6404         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6405              WhitePawn <= toP && toP <= WhiteKing &&
6406              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6407              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6408             (BlackPawn <= fromP && fromP <= BlackKing &&
6409              BlackPawn <= toP && toP <= BlackKing &&
6410              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6411              !(fromP == BlackKing && toP == BlackRook && frc))) {
6412             /* Clicked again on same color piece -- changed his mind */
6413             second = (x == fromX && y == fromY);
6414            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6415             if (appData.highlightDragging) {
6416                 SetHighlights(x, y, -1, -1);
6417             } else {
6418                 ClearHighlights();
6419             }
6420             if (OKToStartUserMove(x, y)) {
6421                 fromX = x;
6422                 fromY = y; dragging = 1;
6423                 MarkTargetSquares(0);
6424                 DragPieceBegin(xPix, yPix);
6425             }
6426             return;
6427            }
6428         }
6429         // ignore clicks on holdings
6430         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6431     }
6432
6433     if (clickType == Release && x == fromX && y == fromY) {
6434         DragPieceEnd(xPix, yPix); dragging = 0;
6435         if (appData.animateDragging) {
6436             /* Undo animation damage if any */
6437             DrawPosition(FALSE, NULL);
6438         }
6439         if (second) {
6440             /* Second up/down in same square; just abort move */
6441             second = 0;
6442             fromX = fromY = -1;
6443             ClearHighlights();
6444             gotPremove = 0;
6445             ClearPremoveHighlights();
6446         } else {
6447             /* First upclick in same square; start click-click mode */
6448             SetHighlights(x, y, -1, -1);
6449         }
6450         return;
6451     }
6452
6453     /* we now have a different from- and (possibly off-board) to-square */
6454     /* Completed move */
6455     toX = x;
6456     toY = y;
6457     saveAnimate = appData.animate;
6458     if (clickType == Press) {
6459         /* Finish clickclick move */
6460         if (appData.animate || appData.highlightLastMove) {
6461             SetHighlights(fromX, fromY, toX, toY);
6462         } else {
6463             ClearHighlights();
6464         }
6465     } else {
6466         /* Finish drag move */
6467         if (appData.highlightLastMove) {
6468             SetHighlights(fromX, fromY, toX, toY);
6469         } else {
6470             ClearHighlights();
6471         }
6472         DragPieceEnd(xPix, yPix); dragging = 0;
6473         /* Don't animate move and drag both */
6474         appData.animate = FALSE;
6475     }
6476
6477     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6478     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6479         ChessSquare piece = boards[currentMove][fromY][fromX];
6480         if(gameMode == EditPosition && piece != EmptySquare &&
6481            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6482             int n;
6483
6484             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6485                 n = PieceToNumber(piece - (int)BlackPawn);
6486                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6487                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6488                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6489             } else
6490             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6491                 n = PieceToNumber(piece);
6492                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6493                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6494                 boards[currentMove][n][BOARD_WIDTH-2]++;
6495             }
6496             boards[currentMove][fromY][fromX] = EmptySquare;
6497         }
6498         ClearHighlights();
6499         fromX = fromY = -1;
6500         DrawPosition(TRUE, boards[currentMove]);
6501         return;
6502     }
6503
6504     // off-board moves should not be highlighted
6505     if(x < 0 || x < 0) ClearHighlights();
6506
6507     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6508         SetHighlights(fromX, fromY, toX, toY);
6509         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6510             // [HGM] super: promotion to captured piece selected from holdings
6511             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6512             promotionChoice = TRUE;
6513             // kludge follows to temporarily execute move on display, without promoting yet
6514             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6515             boards[currentMove][toY][toX] = p;
6516             DrawPosition(FALSE, boards[currentMove]);
6517             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6518             boards[currentMove][toY][toX] = q;
6519             DisplayMessage("Click in holdings to choose piece", "");
6520             return;
6521         }
6522         PromotionPopUp();
6523     } else {
6524         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6525         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6526         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6527         fromX = fromY = -1;
6528     }
6529     appData.animate = saveAnimate;
6530     if (appData.animate || appData.animateDragging) {
6531         /* Undo animation damage if needed */
6532         DrawPosition(FALSE, NULL);
6533     }
6534 }
6535
6536 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6537 {   // front-end-free part taken out of PieceMenuPopup
6538     int whichMenu; int xSqr, ySqr;
6539
6540     if(seekGraphUp) { // [HGM] seekgraph
6541         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6542         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6543         return -2;
6544     }
6545
6546     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6547          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6548         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6549         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6550         if(action == Press)   {
6551             originalFlip = flipView;
6552             flipView = !flipView; // temporarily flip board to see game from partners perspective
6553             DrawPosition(TRUE, partnerBoard);
6554             DisplayMessage(partnerStatus, "");
6555             partnerUp = TRUE;
6556         } else if(action == Release) {
6557             flipView = originalFlip;
6558             DrawPosition(TRUE, boards[currentMove]);
6559             partnerUp = FALSE;
6560         }
6561         return -2;
6562     }
6563
6564     xSqr = EventToSquare(x, BOARD_WIDTH);
6565     ySqr = EventToSquare(y, BOARD_HEIGHT);
6566     if (action == Release) UnLoadPV(); // [HGM] pv
6567     if (action != Press) return -2; // return code to be ignored
6568     switch (gameMode) {
6569       case IcsExamining:
6570         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6571       case EditPosition:
6572         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6573         if (xSqr < 0 || ySqr < 0) return -1;\r
6574         whichMenu = 0; // edit-position menu
6575         break;
6576       case IcsObserving:
6577         if(!appData.icsEngineAnalyze) return -1;
6578       case IcsPlayingWhite:
6579       case IcsPlayingBlack:
6580         if(!appData.zippyPlay) goto noZip;
6581       case AnalyzeMode:
6582       case AnalyzeFile:
6583       case MachinePlaysWhite:
6584       case MachinePlaysBlack:
6585       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6586         if (!appData.dropMenu) {
6587           LoadPV(x, y);
6588           return 2; // flag front-end to grab mouse events
6589         }
6590         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6591            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6592       case EditGame:
6593       noZip:
6594         if (xSqr < 0 || ySqr < 0) return -1;
6595         if (!appData.dropMenu || appData.testLegality &&
6596             gameInfo.variant != VariantBughouse &&
6597             gameInfo.variant != VariantCrazyhouse) return -1;
6598         whichMenu = 1; // drop menu
6599         break;
6600       default:
6601         return -1;
6602     }
6603
6604     if (((*fromX = xSqr) < 0) ||
6605         ((*fromY = ySqr) < 0)) {
6606         *fromX = *fromY = -1;
6607         return -1;
6608     }
6609     if (flipView)
6610       *fromX = BOARD_WIDTH - 1 - *fromX;
6611     else
6612       *fromY = BOARD_HEIGHT - 1 - *fromY;
6613
6614     return whichMenu;
6615 }
6616
6617 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6618 {
6619 //    char * hint = lastHint;
6620     FrontEndProgramStats stats;
6621
6622     stats.which = cps == &first ? 0 : 1;
6623     stats.depth = cpstats->depth;
6624     stats.nodes = cpstats->nodes;
6625     stats.score = cpstats->score;
6626     stats.time = cpstats->time;
6627     stats.pv = cpstats->movelist;
6628     stats.hint = lastHint;
6629     stats.an_move_index = 0;
6630     stats.an_move_count = 0;
6631
6632     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6633         stats.hint = cpstats->move_name;
6634         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6635         stats.an_move_count = cpstats->nr_moves;
6636     }
6637
6638     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
6639
6640     SetProgramStats( &stats );
6641 }
6642
6643 void
6644 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6645 {       // count all piece types
6646         int p, f, r;
6647         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6648         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6649         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6650                 p = board[r][f];
6651                 pCnt[p]++;
6652                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6653                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6654                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6655                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6656                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6657                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6658         }
6659 }
6660
6661 int
6662 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6663 {
6664         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6665         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6666
6667         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6668         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6669         if(myPawns == 2 && nMine == 3) // KPP
6670             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6671         if(myPawns == 1 && nMine == 2) // KP
6672             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6673         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6674             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6675         if(myPawns) return FALSE;
6676         if(pCnt[WhiteRook+side])
6677             return pCnt[BlackRook-side] ||
6678                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6679                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6680                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6681         if(pCnt[WhiteCannon+side]) {
6682             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6683             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6684         }
6685         if(pCnt[WhiteKnight+side])
6686             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6687         return FALSE;
6688 }
6689
6690 int
6691 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6692 {
6693         VariantClass v = gameInfo.variant;
6694
6695         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6696         if(v == VariantShatranj) return TRUE; // always winnable through baring
6697         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6698         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6699
6700         if(v == VariantXiangqi) {
6701                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6702
6703                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6704                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6705                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6706                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6707                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6708                 if(stale) // we have at least one last-rank P plus perhaps C
6709                     return majors // KPKX
6710                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6711                 else // KCA*E*
6712                     return pCnt[WhiteFerz+side] // KCAK
6713                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6714                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6715                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6716
6717         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6718                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6719
6720                 if(nMine == 1) return FALSE; // bare King
6721                 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
6722                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6723                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6724                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6725                 if(pCnt[WhiteKnight+side])
6726                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6727                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6728                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6729                 if(nBishops)
6730                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6731                 if(pCnt[WhiteAlfil+side])
6732                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6733                 if(pCnt[WhiteWazir+side])
6734                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6735         }
6736
6737         return TRUE;
6738 }
6739
6740 int
6741 Adjudicate(ChessProgramState *cps)
6742 {       // [HGM] some adjudications useful with buggy engines
6743         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6744         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6745         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6746         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6747         int k, count = 0; static int bare = 1;
6748         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6749         Boolean canAdjudicate = !appData.icsActive;
6750
6751         // most tests only when we understand the game, i.e. legality-checking on
6752             if( appData.testLegality )
6753             {   /* [HGM] Some more adjudications for obstinate engines */
6754                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6755                 static int moveCount = 6;
6756                 ChessMove result;
6757                 char *reason = NULL;
6758
6759                 /* Count what is on board. */
6760                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6761
6762                 /* Some material-based adjudications that have to be made before stalemate test */
6763                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6764                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6765                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6766                      if(canAdjudicate && appData.checkMates) {
6767                          if(engineOpponent)
6768                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6769                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6770                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6771                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6772                          return 1;
6773                      }
6774                 }
6775
6776                 /* Bare King in Shatranj (loses) or Losers (wins) */
6777                 if( nrW == 1 || nrB == 1) {
6778                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6779                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6780                      if(canAdjudicate && appData.checkMates) {
6781                          if(engineOpponent)
6782                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6783                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6784                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6785                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6786                          return 1;
6787                      }
6788                   } else
6789                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6790                   {    /* bare King */
6791                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6792                         if(canAdjudicate && appData.checkMates) {
6793                             /* but only adjudicate if adjudication enabled */
6794                             if(engineOpponent)
6795                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6796                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6797                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6798                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6799                             return 1;
6800                         }
6801                   }
6802                 } else bare = 1;
6803
6804
6805             // don't wait for engine to announce game end if we can judge ourselves
6806             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6807               case MT_CHECK:
6808                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6809                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6810                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6811                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6812                             checkCnt++;
6813                         if(checkCnt >= 2) {
6814                             reason = "Xboard adjudication: 3rd check";
6815                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6816                             break;
6817                         }
6818                     }
6819                 }
6820               case MT_NONE:
6821               default:
6822                 break;
6823               case MT_STALEMATE:
6824               case MT_STAINMATE:
6825                 reason = "Xboard adjudication: Stalemate";
6826                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6827                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6828                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6829                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6830                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6831                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6832                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6833                                                                         EP_CHECKMATE : EP_WINS);
6834                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6835                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6836                 }
6837                 break;
6838               case MT_CHECKMATE:
6839                 reason = "Xboard adjudication: Checkmate";
6840                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6841                 break;
6842             }
6843
6844                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6845                     case EP_STALEMATE:
6846                         result = GameIsDrawn; break;
6847                     case EP_CHECKMATE:
6848                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6849                     case EP_WINS:
6850                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6851                     default:
6852                         result = EndOfFile;
6853                 }
6854                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6855                     if(engineOpponent)
6856                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6857                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6858                     GameEnds( result, reason, GE_XBOARD );
6859                     return 1;
6860                 }
6861
6862                 /* Next absolutely insufficient mating material. */
6863                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6864                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6865                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6866
6867                      /* always flag draws, for judging claims */
6868                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6869
6870                      if(canAdjudicate && appData.materialDraws) {
6871                          /* but only adjudicate them if adjudication enabled */
6872                          if(engineOpponent) {
6873                            SendToProgram("force\n", engineOpponent); // suppress reply
6874                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6875                          }
6876                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6877                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6878                          return 1;
6879                      }
6880                 }
6881
6882                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6883                 if(gameInfo.variant == VariantXiangqi ?
6884                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6885                  : nrW + nrB == 4 &&
6886                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6887                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6888                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6889                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6890                    ) ) {
6891                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6892                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6893                           if(engineOpponent) {
6894                             SendToProgram("force\n", engineOpponent); // suppress reply
6895                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6896                           }
6897                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6898                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6899                           return 1;
6900                      }
6901                 } else moveCount = 6;
6902             }
6903         if (appData.debugMode) { int i;
6904             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6905                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6906                     appData.drawRepeats);
6907             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6908               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6909
6910         }
6911
6912         // Repetition draws and 50-move rule can be applied independently of legality testing
6913
6914                 /* Check for rep-draws */
6915                 count = 0;
6916                 for(k = forwardMostMove-2;
6917                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6918                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6919                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6920                     k-=2)
6921                 {   int rights=0;
6922                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6923                         /* compare castling rights */
6924                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6925                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6926                                 rights++; /* King lost rights, while rook still had them */
6927                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6928                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6929                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6930                                    rights++; /* but at least one rook lost them */
6931                         }
6932                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6933                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6934                                 rights++;
6935                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6936                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6937                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6938                                    rights++;
6939                         }
6940                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6941                             && appData.drawRepeats > 1) {
6942                              /* adjudicate after user-specified nr of repeats */
6943                              int result = GameIsDrawn;
6944                              char *details = "XBoard adjudication: repetition draw";
6945                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6946                                 // [HGM] xiangqi: check for forbidden perpetuals
6947                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6948                                 for(m=forwardMostMove; m>k; m-=2) {
6949                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6950                                         ourPerpetual = 0; // the current mover did not always check
6951                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6952                                         hisPerpetual = 0; // the opponent did not always check
6953                                 }
6954                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6955                                                                         ourPerpetual, hisPerpetual);
6956                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6957                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6958                                     details = "Xboard adjudication: perpetual checking";
6959                                 } else
6960                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6961                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6962                                 } else
6963                                 // Now check for perpetual chases
6964                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6965                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6966                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6967                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6968                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6969                                         details = "Xboard adjudication: perpetual chasing";
6970                                     } else
6971                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6972                                         break; // Abort repetition-checking loop.
6973                                 }
6974                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6975                              }
6976                              if(engineOpponent) {
6977                                SendToProgram("force\n", engineOpponent); // suppress reply
6978                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6979                              }
6980                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6981                              GameEnds( result, details, GE_XBOARD );
6982                              return 1;
6983                         }
6984                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6985                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6986                     }
6987                 }
6988
6989                 /* Now we test for 50-move draws. Determine ply count */
6990                 count = forwardMostMove;
6991                 /* look for last irreversble move */
6992                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6993                     count--;
6994                 /* if we hit starting position, add initial plies */
6995                 if( count == backwardMostMove )
6996                     count -= initialRulePlies;
6997                 count = forwardMostMove - count;
6998                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6999                         // adjust reversible move counter for checks in Xiangqi
7000                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7001                         if(i < backwardMostMove) i = backwardMostMove;
7002                         while(i <= forwardMostMove) {
7003                                 lastCheck = inCheck; // check evasion does not count
7004                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7005                                 if(inCheck || lastCheck) count--; // check does not count
7006                                 i++;
7007                         }
7008                 }
7009                 if( count >= 100)
7010                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7011                          /* this is used to judge if draw claims are legal */
7012                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7013                          if(engineOpponent) {
7014                            SendToProgram("force\n", engineOpponent); // suppress reply
7015                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7016                          }
7017                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7018                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7019                          return 1;
7020                 }
7021
7022                 /* if draw offer is pending, treat it as a draw claim
7023                  * when draw condition present, to allow engines a way to
7024                  * claim draws before making their move to avoid a race
7025                  * condition occurring after their move
7026                  */
7027                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7028                          char *p = NULL;
7029                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7030                              p = "Draw claim: 50-move rule";
7031                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7032                              p = "Draw claim: 3-fold repetition";
7033                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7034                              p = "Draw claim: insufficient mating material";
7035                          if( p != NULL && canAdjudicate) {
7036                              if(engineOpponent) {
7037                                SendToProgram("force\n", engineOpponent); // suppress reply
7038                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7039                              }
7040                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7041                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7042                              return 1;
7043                          }
7044                 }
7045
7046                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7047                     if(engineOpponent) {
7048                       SendToProgram("force\n", engineOpponent); // suppress reply
7049                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7050                     }
7051                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7052                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7053                     return 1;
7054                 }
7055         return 0;
7056 }
7057
7058 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7059 {   // [HGM] book: this routine intercepts moves to simulate book replies
7060     char *bookHit = NULL;
7061
7062     //first determine if the incoming move brings opponent into his book
7063     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7064         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7065     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7066     if(bookHit != NULL && !cps->bookSuspend) {
7067         // make sure opponent is not going to reply after receiving move to book position
7068         SendToProgram("force\n", cps);
7069         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7070     }
7071     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7072     // now arrange restart after book miss
7073     if(bookHit) {
7074         // after a book hit we never send 'go', and the code after the call to this routine
7075         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7076         char buf[MSG_SIZ];
7077         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7078         SendToProgram(buf, cps);
7079         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7080     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7081         SendToProgram("go\n", cps);
7082         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7083     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7084         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7085             SendToProgram("go\n", cps);
7086         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7087     }
7088     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7089 }
7090
7091 char *savedMessage;
7092 ChessProgramState *savedState;
7093 void DeferredBookMove(void)
7094 {
7095         if(savedState->lastPing != savedState->lastPong)
7096                     ScheduleDelayedEvent(DeferredBookMove, 10);
7097         else
7098         HandleMachineMove(savedMessage, savedState);
7099 }
7100
7101 void
7102 HandleMachineMove(message, cps)
7103      char *message;
7104      ChessProgramState *cps;
7105 {
7106     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7107     char realname[MSG_SIZ];
7108     int fromX, fromY, toX, toY;
7109     ChessMove moveType;
7110     char promoChar;
7111     char *p;
7112     int machineWhite;
7113     char *bookHit;
7114
7115     cps->userError = 0;
7116
7117 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7118     /*
7119      * Kludge to ignore BEL characters
7120      */
7121     while (*message == '\007') message++;
7122
7123     /*
7124      * [HGM] engine debug message: ignore lines starting with '#' character
7125      */
7126     if(cps->debug && *message == '#') return;
7127
7128     /*
7129      * Look for book output
7130      */
7131     if (cps == &first && bookRequested) {
7132         if (message[0] == '\t' || message[0] == ' ') {
7133             /* Part of the book output is here; append it */
7134             strcat(bookOutput, message);
7135             strcat(bookOutput, "  \n");
7136             return;
7137         } else if (bookOutput[0] != NULLCHAR) {
7138             /* All of book output has arrived; display it */
7139             char *p = bookOutput;
7140             while (*p != NULLCHAR) {
7141                 if (*p == '\t') *p = ' ';
7142                 p++;
7143             }
7144             DisplayInformation(bookOutput);
7145             bookRequested = FALSE;
7146             /* Fall through to parse the current output */
7147         }
7148     }
7149
7150     /*
7151      * Look for machine move.
7152      */
7153     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7154         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7155     {
7156         /* This method is only useful on engines that support ping */
7157         if (cps->lastPing != cps->lastPong) {
7158           if (gameMode == BeginningOfGame) {
7159             /* Extra move from before last new; ignore */
7160             if (appData.debugMode) {
7161                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7162             }
7163           } else {
7164             if (appData.debugMode) {
7165                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7166                         cps->which, gameMode);
7167             }
7168
7169             SendToProgram("undo\n", cps);
7170           }
7171           return;
7172         }
7173
7174         switch (gameMode) {
7175           case BeginningOfGame:
7176             /* Extra move from before last reset; ignore */
7177             if (appData.debugMode) {
7178                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7179             }
7180             return;
7181
7182           case EndOfGame:
7183           case IcsIdle:
7184           default:
7185             /* Extra move after we tried to stop.  The mode test is
7186                not a reliable way of detecting this problem, but it's
7187                the best we can do on engines that don't support ping.
7188             */
7189             if (appData.debugMode) {
7190                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7191                         cps->which, gameMode);
7192             }
7193             SendToProgram("undo\n", cps);
7194             return;
7195
7196           case MachinePlaysWhite:
7197           case IcsPlayingWhite:
7198             machineWhite = TRUE;
7199             break;
7200
7201           case MachinePlaysBlack:
7202           case IcsPlayingBlack:
7203             machineWhite = FALSE;
7204             break;
7205
7206           case TwoMachinesPlay:
7207             machineWhite = (cps->twoMachinesColor[0] == 'w');
7208             break;
7209         }
7210         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7211             if (appData.debugMode) {
7212                 fprintf(debugFP,
7213                         "Ignoring move out of turn by %s, gameMode %d"
7214                         ", forwardMost %d\n",
7215                         cps->which, gameMode, forwardMostMove);
7216             }
7217             return;
7218         }
7219
7220     if (appData.debugMode) { int f = forwardMostMove;
7221         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7222                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7223                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7224     }
7225         if(cps->alphaRank) AlphaRank(machineMove, 4);
7226         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7227                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7228             /* Machine move could not be parsed; ignore it. */
7229           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7230                     machineMove, cps->which);
7231             DisplayError(buf1, 0);
7232             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7233                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7234             if (gameMode == TwoMachinesPlay) {
7235               GameEnds(machineWhite ? BlackWins : WhiteWins,
7236                        buf1, GE_XBOARD);
7237             }
7238             return;
7239         }
7240
7241         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7242         /* So we have to redo legality test with true e.p. status here,  */
7243         /* to make sure an illegal e.p. capture does not slip through,   */
7244         /* to cause a forfeit on a justified illegal-move complaint      */
7245         /* of the opponent.                                              */
7246         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7247            ChessMove moveType;
7248            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7249                              fromY, fromX, toY, toX, promoChar);
7250             if (appData.debugMode) {
7251                 int i;
7252                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7253                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7254                 fprintf(debugFP, "castling rights\n");
7255             }
7256             if(moveType == IllegalMove) {
7257               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7258                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7259                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7260                            buf1, GE_XBOARD);
7261                 return;
7262            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7263            /* [HGM] Kludge to handle engines that send FRC-style castling
7264               when they shouldn't (like TSCP-Gothic) */
7265            switch(moveType) {
7266              case WhiteASideCastleFR:
7267              case BlackASideCastleFR:
7268                toX+=2;
7269                currentMoveString[2]++;
7270                break;
7271              case WhiteHSideCastleFR:
7272              case BlackHSideCastleFR:
7273                toX--;
7274                currentMoveString[2]--;
7275                break;
7276              default: ; // nothing to do, but suppresses warning of pedantic compilers
7277            }
7278         }
7279         hintRequested = FALSE;
7280         lastHint[0] = NULLCHAR;
7281         bookRequested = FALSE;
7282         /* Program may be pondering now */
7283         cps->maybeThinking = TRUE;
7284         if (cps->sendTime == 2) cps->sendTime = 1;
7285         if (cps->offeredDraw) cps->offeredDraw--;
7286
7287         /* currentMoveString is set as a side-effect of ParseOneMove */
7288         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7289         strcat(machineMove, "\n");
7290         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7291
7292         /* [AS] Save move info*/
7293         pvInfoList[ forwardMostMove ].score = programStats.score;
7294         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7295         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7296
7297         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7298
7299         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7300         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7301             int count = 0;
7302
7303             while( count < adjudicateLossPlies ) {
7304                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7305
7306                 if( count & 1 ) {
7307                     score = -score; /* Flip score for winning side */
7308                 }
7309
7310                 if( score > adjudicateLossThreshold ) {
7311                     break;
7312                 }
7313
7314                 count++;
7315             }
7316
7317             if( count >= adjudicateLossPlies ) {
7318                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7319
7320                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7321                     "Xboard adjudication",
7322                     GE_XBOARD );
7323
7324                 return;
7325             }
7326         }
7327
7328         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7329
7330 #if ZIPPY
7331         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7332             first.initDone) {
7333           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7334                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7335                 SendToICS("draw ");
7336                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7337           }
7338           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7339           ics_user_moved = 1;
7340           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7341                 char buf[3*MSG_SIZ];
7342
7343                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7344                         programStats.score / 100.,
7345                         programStats.depth,
7346                         programStats.time / 100.,
7347                         (unsigned int)programStats.nodes,
7348                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7349                         programStats.movelist);
7350                 SendToICS(buf);
7351 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7352           }
7353         }
7354 #endif
7355
7356         /* [AS] Clear stats for next move */
7357         ClearProgramStats();
7358         thinkOutput[0] = NULLCHAR;
7359         hiddenThinkOutputState = 0;
7360
7361         bookHit = NULL;
7362         if (gameMode == TwoMachinesPlay) {
7363             /* [HGM] relaying draw offers moved to after reception of move */
7364             /* and interpreting offer as claim if it brings draw condition */
7365             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7366                 SendToProgram("draw\n", cps->other);
7367             }
7368             if (cps->other->sendTime) {
7369                 SendTimeRemaining(cps->other,
7370                                   cps->other->twoMachinesColor[0] == 'w');
7371             }
7372             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7373             if (firstMove && !bookHit) {
7374                 firstMove = FALSE;
7375                 if (cps->other->useColors) {
7376                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7377                 }
7378                 SendToProgram("go\n", cps->other);
7379             }
7380             cps->other->maybeThinking = TRUE;
7381         }
7382
7383         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7384
7385         if (!pausing && appData.ringBellAfterMoves) {
7386             RingBell();
7387         }
7388
7389         /*
7390          * Reenable menu items that were disabled while
7391          * machine was thinking
7392          */
7393         if (gameMode != TwoMachinesPlay)
7394             SetUserThinkingEnables();
7395
7396         // [HGM] book: after book hit opponent has received move and is now in force mode
7397         // force the book reply into it, and then fake that it outputted this move by jumping
7398         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7399         if(bookHit) {
7400                 static char bookMove[MSG_SIZ]; // a bit generous?
7401
7402                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7403                 strcat(bookMove, bookHit);
7404                 message = bookMove;
7405                 cps = cps->other;
7406                 programStats.nodes = programStats.depth = programStats.time =
7407                 programStats.score = programStats.got_only_move = 0;
7408                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7409
7410                 if(cps->lastPing != cps->lastPong) {
7411                     savedMessage = message; // args for deferred call
7412                     savedState = cps;
7413                     ScheduleDelayedEvent(DeferredBookMove, 10);
7414                     return;
7415                 }
7416                 goto FakeBookMove;
7417         }
7418
7419         return;
7420     }
7421
7422     /* Set special modes for chess engines.  Later something general
7423      *  could be added here; for now there is just one kludge feature,
7424      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7425      *  when "xboard" is given as an interactive command.
7426      */
7427     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7428         cps->useSigint = FALSE;
7429         cps->useSigterm = FALSE;
7430     }
7431     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7432       ParseFeatures(message+8, cps);
7433       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7434     }
7435
7436     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7437      * want this, I was asked to put it in, and obliged.
7438      */
7439     if (!strncmp(message, "setboard ", 9)) {
7440         Board initial_position;
7441
7442         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7443
7444         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7445             DisplayError(_("Bad FEN received from engine"), 0);
7446             return ;
7447         } else {
7448            Reset(TRUE, FALSE);
7449            CopyBoard(boards[0], initial_position);
7450            initialRulePlies = FENrulePlies;
7451            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7452            else gameMode = MachinePlaysBlack;
7453            DrawPosition(FALSE, boards[currentMove]);
7454         }
7455         return;
7456     }
7457
7458     /*
7459      * Look for communication commands
7460      */
7461     if (!strncmp(message, "telluser ", 9)) {
7462         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7463         DisplayNote(message + 9);
7464         return;
7465     }
7466     if (!strncmp(message, "tellusererror ", 14)) {
7467         cps->userError = 1;
7468         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7469         DisplayError(message + 14, 0);
7470         return;
7471     }
7472     if (!strncmp(message, "tellopponent ", 13)) {
7473       if (appData.icsActive) {
7474         if (loggedOn) {
7475           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7476           SendToICS(buf1);
7477         }
7478       } else {
7479         DisplayNote(message + 13);
7480       }
7481       return;
7482     }
7483     if (!strncmp(message, "tellothers ", 11)) {
7484       if (appData.icsActive) {
7485         if (loggedOn) {
7486           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7487           SendToICS(buf1);
7488         }
7489       }
7490       return;
7491     }
7492     if (!strncmp(message, "tellall ", 8)) {
7493       if (appData.icsActive) {
7494         if (loggedOn) {
7495           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7496           SendToICS(buf1);
7497         }
7498       } else {
7499         DisplayNote(message + 8);
7500       }
7501       return;
7502     }
7503     if (strncmp(message, "warning", 7) == 0) {
7504         /* Undocumented feature, use tellusererror in new code */
7505         DisplayError(message, 0);
7506         return;
7507     }
7508     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7509         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7510         strcat(realname, " query");
7511         AskQuestion(realname, buf2, buf1, cps->pr);
7512         return;
7513     }
7514     /* Commands from the engine directly to ICS.  We don't allow these to be
7515      *  sent until we are logged on. Crafty kibitzes have been known to
7516      *  interfere with the login process.
7517      */
7518     if (loggedOn) {
7519         if (!strncmp(message, "tellics ", 8)) {
7520             SendToICS(message + 8);
7521             SendToICS("\n");
7522             return;
7523         }
7524         if (!strncmp(message, "tellicsnoalias ", 15)) {
7525             SendToICS(ics_prefix);
7526             SendToICS(message + 15);
7527             SendToICS("\n");
7528             return;
7529         }
7530         /* The following are for backward compatibility only */
7531         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7532             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7533             SendToICS(ics_prefix);
7534             SendToICS(message);
7535             SendToICS("\n");
7536             return;
7537         }
7538     }
7539     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7540         return;
7541     }
7542     /*
7543      * If the move is illegal, cancel it and redraw the board.
7544      * Also deal with other error cases.  Matching is rather loose
7545      * here to accommodate engines written before the spec.
7546      */
7547     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7548         strncmp(message, "Error", 5) == 0) {
7549         if (StrStr(message, "name") ||
7550             StrStr(message, "rating") || StrStr(message, "?") ||
7551             StrStr(message, "result") || StrStr(message, "board") ||
7552             StrStr(message, "bk") || StrStr(message, "computer") ||
7553             StrStr(message, "variant") || StrStr(message, "hint") ||
7554             StrStr(message, "random") || StrStr(message, "depth") ||
7555             StrStr(message, "accepted")) {
7556             return;
7557         }
7558         if (StrStr(message, "protover")) {
7559           /* Program is responding to input, so it's apparently done
7560              initializing, and this error message indicates it is
7561              protocol version 1.  So we don't need to wait any longer
7562              for it to initialize and send feature commands. */
7563           FeatureDone(cps, 1);
7564           cps->protocolVersion = 1;
7565           return;
7566         }
7567         cps->maybeThinking = FALSE;
7568
7569         if (StrStr(message, "draw")) {
7570             /* Program doesn't have "draw" command */
7571             cps->sendDrawOffers = 0;
7572             return;
7573         }
7574         if (cps->sendTime != 1 &&
7575             (StrStr(message, "time") || StrStr(message, "otim"))) {
7576           /* Program apparently doesn't have "time" or "otim" command */
7577           cps->sendTime = 0;
7578           return;
7579         }
7580         if (StrStr(message, "analyze")) {
7581             cps->analysisSupport = FALSE;
7582             cps->analyzing = FALSE;
7583             Reset(FALSE, TRUE);
7584             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7585             DisplayError(buf2, 0);
7586             return;
7587         }
7588         if (StrStr(message, "(no matching move)st")) {
7589           /* Special kludge for GNU Chess 4 only */
7590           cps->stKludge = TRUE;
7591           SendTimeControl(cps, movesPerSession, timeControl,
7592                           timeIncrement, appData.searchDepth,
7593                           searchTime);
7594           return;
7595         }
7596         if (StrStr(message, "(no matching move)sd")) {
7597           /* Special kludge for GNU Chess 4 only */
7598           cps->sdKludge = TRUE;
7599           SendTimeControl(cps, movesPerSession, timeControl,
7600                           timeIncrement, appData.searchDepth,
7601                           searchTime);
7602           return;
7603         }
7604         if (!StrStr(message, "llegal")) {
7605             return;
7606         }
7607         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7608             gameMode == IcsIdle) return;
7609         if (forwardMostMove <= backwardMostMove) return;
7610         if (pausing) PauseEvent();
7611       if(appData.forceIllegal) {
7612             // [HGM] illegal: machine refused move; force position after move into it
7613           SendToProgram("force\n", cps);
7614           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7615                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7616                 // when black is to move, while there might be nothing on a2 or black
7617                 // might already have the move. So send the board as if white has the move.
7618                 // But first we must change the stm of the engine, as it refused the last move
7619                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7620                 if(WhiteOnMove(forwardMostMove)) {
7621                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7622                     SendBoard(cps, forwardMostMove); // kludgeless board
7623                 } else {
7624                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7625                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7626                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7627                 }
7628           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7629             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7630                  gameMode == TwoMachinesPlay)
7631               SendToProgram("go\n", cps);
7632             return;
7633       } else
7634         if (gameMode == PlayFromGameFile) {
7635             /* Stop reading this game file */
7636             gameMode = EditGame;
7637             ModeHighlight();
7638         }
7639         currentMove = forwardMostMove-1;
7640         DisplayMove(currentMove-1); /* before DisplayMoveError */
7641         SwitchClocks(forwardMostMove-1); // [HGM] race
7642         DisplayBothClocks();
7643         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7644                 parseList[currentMove], cps->which);
7645         DisplayMoveError(buf1);
7646         DrawPosition(FALSE, boards[currentMove]);
7647
7648         /* [HGM] illegal-move claim should forfeit game when Xboard */
7649         /* only passes fully legal moves                            */
7650         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7651             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7652                                 "False illegal-move claim", GE_XBOARD );
7653         }
7654         return;
7655     }
7656     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7657         /* Program has a broken "time" command that
7658            outputs a string not ending in newline.
7659            Don't use it. */
7660         cps->sendTime = 0;
7661     }
7662
7663     /*
7664      * If chess program startup fails, exit with an error message.
7665      * Attempts to recover here are futile.
7666      */
7667     if ((StrStr(message, "unknown host") != NULL)
7668         || (StrStr(message, "No remote directory") != NULL)
7669         || (StrStr(message, "not found") != NULL)
7670         || (StrStr(message, "No such file") != NULL)
7671         || (StrStr(message, "can't alloc") != NULL)
7672         || (StrStr(message, "Permission denied") != NULL)) {
7673
7674         cps->maybeThinking = FALSE;
7675         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7676                 cps->which, cps->program, cps->host, message);
7677         RemoveInputSource(cps->isr);
7678         DisplayFatalError(buf1, 0, 1);
7679         return;
7680     }
7681
7682     /*
7683      * Look for hint output
7684      */
7685     if (sscanf(message, "Hint: %s", buf1) == 1) {
7686         if (cps == &first && hintRequested) {
7687             hintRequested = FALSE;
7688             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7689                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7690                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7691                                     PosFlags(forwardMostMove),
7692                                     fromY, fromX, toY, toX, promoChar, buf1);
7693                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7694                 DisplayInformation(buf2);
7695             } else {
7696                 /* Hint move could not be parsed!? */
7697               snprintf(buf2, sizeof(buf2),
7698                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7699                         buf1, cps->which);
7700                 DisplayError(buf2, 0);
7701             }
7702         } else {
7703           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7704         }
7705         return;
7706     }
7707
7708     /*
7709      * Ignore other messages if game is not in progress
7710      */
7711     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7712         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7713
7714     /*
7715      * look for win, lose, draw, or draw offer
7716      */
7717     if (strncmp(message, "1-0", 3) == 0) {
7718         char *p, *q, *r = "";
7719         p = strchr(message, '{');
7720         if (p) {
7721             q = strchr(p, '}');
7722             if (q) {
7723                 *q = NULLCHAR;
7724                 r = p + 1;
7725             }
7726         }
7727         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7728         return;
7729     } else if (strncmp(message, "0-1", 3) == 0) {
7730         char *p, *q, *r = "";
7731         p = strchr(message, '{');
7732         if (p) {
7733             q = strchr(p, '}');
7734             if (q) {
7735                 *q = NULLCHAR;
7736                 r = p + 1;
7737             }
7738         }
7739         /* Kludge for Arasan 4.1 bug */
7740         if (strcmp(r, "Black resigns") == 0) {
7741             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7742             return;
7743         }
7744         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7745         return;
7746     } else if (strncmp(message, "1/2", 3) == 0) {
7747         char *p, *q, *r = "";
7748         p = strchr(message, '{');
7749         if (p) {
7750             q = strchr(p, '}');
7751             if (q) {
7752                 *q = NULLCHAR;
7753                 r = p + 1;
7754             }
7755         }
7756
7757         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7758         return;
7759
7760     } else if (strncmp(message, "White resign", 12) == 0) {
7761         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7762         return;
7763     } else if (strncmp(message, "Black resign", 12) == 0) {
7764         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7765         return;
7766     } else if (strncmp(message, "White matches", 13) == 0 ||
7767                strncmp(message, "Black matches", 13) == 0   ) {
7768         /* [HGM] ignore GNUShogi noises */
7769         return;
7770     } else if (strncmp(message, "White", 5) == 0 &&
7771                message[5] != '(' &&
7772                StrStr(message, "Black") == NULL) {
7773         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7774         return;
7775     } else if (strncmp(message, "Black", 5) == 0 &&
7776                message[5] != '(') {
7777         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7778         return;
7779     } else if (strcmp(message, "resign") == 0 ||
7780                strcmp(message, "computer resigns") == 0) {
7781         switch (gameMode) {
7782           case MachinePlaysBlack:
7783           case IcsPlayingBlack:
7784             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7785             break;
7786           case MachinePlaysWhite:
7787           case IcsPlayingWhite:
7788             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7789             break;
7790           case TwoMachinesPlay:
7791             if (cps->twoMachinesColor[0] == 'w')
7792               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7793             else
7794               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7795             break;
7796           default:
7797             /* can't happen */
7798             break;
7799         }
7800         return;
7801     } else if (strncmp(message, "opponent mates", 14) == 0) {
7802         switch (gameMode) {
7803           case MachinePlaysBlack:
7804           case IcsPlayingBlack:
7805             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7806             break;
7807           case MachinePlaysWhite:
7808           case IcsPlayingWhite:
7809             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7810             break;
7811           case TwoMachinesPlay:
7812             if (cps->twoMachinesColor[0] == 'w')
7813               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7814             else
7815               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7816             break;
7817           default:
7818             /* can't happen */
7819             break;
7820         }
7821         return;
7822     } else if (strncmp(message, "computer mates", 14) == 0) {
7823         switch (gameMode) {
7824           case MachinePlaysBlack:
7825           case IcsPlayingBlack:
7826             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7827             break;
7828           case MachinePlaysWhite:
7829           case IcsPlayingWhite:
7830             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7831             break;
7832           case TwoMachinesPlay:
7833             if (cps->twoMachinesColor[0] == 'w')
7834               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7835             else
7836               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7837             break;
7838           default:
7839             /* can't happen */
7840             break;
7841         }
7842         return;
7843     } else if (strncmp(message, "checkmate", 9) == 0) {
7844         if (WhiteOnMove(forwardMostMove)) {
7845             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7846         } else {
7847             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7848         }
7849         return;
7850     } else if (strstr(message, "Draw") != NULL ||
7851                strstr(message, "game is a draw") != NULL) {
7852         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7853         return;
7854     } else if (strstr(message, "offer") != NULL &&
7855                strstr(message, "draw") != NULL) {
7856 #if ZIPPY
7857         if (appData.zippyPlay && first.initDone) {
7858             /* Relay offer to ICS */
7859             SendToICS(ics_prefix);
7860             SendToICS("draw\n");
7861         }
7862 #endif
7863         cps->offeredDraw = 2; /* valid until this engine moves twice */
7864         if (gameMode == TwoMachinesPlay) {
7865             if (cps->other->offeredDraw) {
7866                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7867             /* [HGM] in two-machine mode we delay relaying draw offer      */
7868             /* until after we also have move, to see if it is really claim */
7869             }
7870         } else if (gameMode == MachinePlaysWhite ||
7871                    gameMode == MachinePlaysBlack) {
7872           if (userOfferedDraw) {
7873             DisplayInformation(_("Machine accepts your draw offer"));
7874             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7875           } else {
7876             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7877           }
7878         }
7879     }
7880
7881
7882     /*
7883      * Look for thinking output
7884      */
7885     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7886           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7887                                 ) {
7888         int plylev, mvleft, mvtot, curscore, time;
7889         char mvname[MOVE_LEN];
7890         u64 nodes; // [DM]
7891         char plyext;
7892         int ignore = FALSE;
7893         int prefixHint = FALSE;
7894         mvname[0] = NULLCHAR;
7895
7896         switch (gameMode) {
7897           case MachinePlaysBlack:
7898           case IcsPlayingBlack:
7899             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7900             break;
7901           case MachinePlaysWhite:
7902           case IcsPlayingWhite:
7903             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7904             break;
7905           case AnalyzeMode:
7906           case AnalyzeFile:
7907             break;
7908           case IcsObserving: /* [DM] icsEngineAnalyze */
7909             if (!appData.icsEngineAnalyze) ignore = TRUE;
7910             break;
7911           case TwoMachinesPlay:
7912             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7913                 ignore = TRUE;
7914             }
7915             break;
7916           default:
7917             ignore = TRUE;
7918             break;
7919         }
7920
7921         if (!ignore) {
7922             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7923             buf1[0] = NULLCHAR;
7924             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7925                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7926
7927                 if (plyext != ' ' && plyext != '\t') {
7928                     time *= 100;
7929                 }
7930
7931                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7932                 if( cps->scoreIsAbsolute &&
7933                     ( gameMode == MachinePlaysBlack ||
7934                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7935                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7936                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7937                      !WhiteOnMove(currentMove)
7938                     ) )
7939                 {
7940                     curscore = -curscore;
7941                 }
7942
7943
7944                 tempStats.depth = plylev;
7945                 tempStats.nodes = nodes;
7946                 tempStats.time = time;
7947                 tempStats.score = curscore;
7948                 tempStats.got_only_move = 0;
7949
7950                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7951                         int ticklen;
7952
7953                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7954                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7955                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7956                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7957                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7958                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7959                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7960                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7961                 }
7962
7963                 /* Buffer overflow protection */
7964                 if (buf1[0] != NULLCHAR) {
7965                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7966                         && appData.debugMode) {
7967                         fprintf(debugFP,
7968                                 "PV is too long; using the first %u bytes.\n",
7969                                 (unsigned) sizeof(tempStats.movelist) - 1);
7970                     }
7971
7972                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
7973                 } else {
7974                     sprintf(tempStats.movelist, " no PV\n");
7975                 }
7976
7977                 if (tempStats.seen_stat) {
7978                     tempStats.ok_to_send = 1;
7979                 }
7980
7981                 if (strchr(tempStats.movelist, '(') != NULL) {
7982                     tempStats.line_is_book = 1;
7983                     tempStats.nr_moves = 0;
7984                     tempStats.moves_left = 0;
7985                 } else {
7986                     tempStats.line_is_book = 0;
7987                 }
7988
7989                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7990                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7991
7992                 SendProgramStatsToFrontend( cps, &tempStats );
7993
7994                 /*
7995                     [AS] Protect the thinkOutput buffer from overflow... this
7996                     is only useful if buf1 hasn't overflowed first!
7997                 */
7998                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
7999                          plylev,
8000                          (gameMode == TwoMachinesPlay ?
8001                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8002                          ((double) curscore) / 100.0,
8003                          prefixHint ? lastHint : "",
8004                          prefixHint ? " " : "" );
8005
8006                 if( buf1[0] != NULLCHAR ) {
8007                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8008
8009                     if( strlen(buf1) > max_len ) {
8010                         if( appData.debugMode) {
8011                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8012                         }
8013                         buf1[max_len+1] = '\0';
8014                     }
8015
8016                     strcat( thinkOutput, buf1 );
8017                 }
8018
8019                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8020                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8021                     DisplayMove(currentMove - 1);
8022                 }
8023                 return;
8024
8025             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8026                 /* crafty (9.25+) says "(only move) <move>"
8027                  * if there is only 1 legal move
8028                  */
8029                 sscanf(p, "(only move) %s", buf1);
8030                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8031                 sprintf(programStats.movelist, "%s (only move)", buf1);
8032                 programStats.depth = 1;
8033                 programStats.nr_moves = 1;
8034                 programStats.moves_left = 1;
8035                 programStats.nodes = 1;
8036                 programStats.time = 1;
8037                 programStats.got_only_move = 1;
8038
8039                 /* Not really, but we also use this member to
8040                    mean "line isn't going to change" (Crafty
8041                    isn't searching, so stats won't change) */
8042                 programStats.line_is_book = 1;
8043
8044                 SendProgramStatsToFrontend( cps, &programStats );
8045
8046                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8047                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8048                     DisplayMove(currentMove - 1);
8049                 }
8050                 return;
8051             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8052                               &time, &nodes, &plylev, &mvleft,
8053                               &mvtot, mvname) >= 5) {
8054                 /* The stat01: line is from Crafty (9.29+) in response
8055                    to the "." command */
8056                 programStats.seen_stat = 1;
8057                 cps->maybeThinking = TRUE;
8058
8059                 if (programStats.got_only_move || !appData.periodicUpdates)
8060                   return;
8061
8062                 programStats.depth = plylev;
8063                 programStats.time = time;
8064                 programStats.nodes = nodes;
8065                 programStats.moves_left = mvleft;
8066                 programStats.nr_moves = mvtot;
8067                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8068                 programStats.ok_to_send = 1;
8069                 programStats.movelist[0] = '\0';
8070
8071                 SendProgramStatsToFrontend( cps, &programStats );
8072
8073                 return;
8074
8075             } else if (strncmp(message,"++",2) == 0) {
8076                 /* Crafty 9.29+ outputs this */
8077                 programStats.got_fail = 2;
8078                 return;
8079
8080             } else if (strncmp(message,"--",2) == 0) {
8081                 /* Crafty 9.29+ outputs this */
8082                 programStats.got_fail = 1;
8083                 return;
8084
8085             } else if (thinkOutput[0] != NULLCHAR &&
8086                        strncmp(message, "    ", 4) == 0) {
8087                 unsigned message_len;
8088
8089                 p = message;
8090                 while (*p && *p == ' ') p++;
8091
8092                 message_len = strlen( p );
8093
8094                 /* [AS] Avoid buffer overflow */
8095                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8096                     strcat(thinkOutput, " ");
8097                     strcat(thinkOutput, p);
8098                 }
8099
8100                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8101                     strcat(programStats.movelist, " ");
8102                     strcat(programStats.movelist, p);
8103                 }
8104
8105                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8106                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8107                     DisplayMove(currentMove - 1);
8108                 }
8109                 return;
8110             }
8111         }
8112         else {
8113             buf1[0] = NULLCHAR;
8114
8115             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8116                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8117             {
8118                 ChessProgramStats cpstats;
8119
8120                 if (plyext != ' ' && plyext != '\t') {
8121                     time *= 100;
8122                 }
8123
8124                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8125                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8126                     curscore = -curscore;
8127                 }
8128
8129                 cpstats.depth = plylev;
8130                 cpstats.nodes = nodes;
8131                 cpstats.time = time;
8132                 cpstats.score = curscore;
8133                 cpstats.got_only_move = 0;
8134                 cpstats.movelist[0] = '\0';
8135
8136                 if (buf1[0] != NULLCHAR) {
8137                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8138                 }
8139
8140                 cpstats.ok_to_send = 0;
8141                 cpstats.line_is_book = 0;
8142                 cpstats.nr_moves = 0;
8143                 cpstats.moves_left = 0;
8144
8145                 SendProgramStatsToFrontend( cps, &cpstats );
8146             }
8147         }
8148     }
8149 }
8150
8151
8152 /* Parse a game score from the character string "game", and
8153    record it as the history of the current game.  The game
8154    score is NOT assumed to start from the standard position.
8155    The display is not updated in any way.
8156    */
8157 void
8158 ParseGameHistory(game)
8159      char *game;
8160 {
8161     ChessMove moveType;
8162     int fromX, fromY, toX, toY, boardIndex;
8163     char promoChar;
8164     char *p, *q;
8165     char buf[MSG_SIZ];
8166
8167     if (appData.debugMode)
8168       fprintf(debugFP, "Parsing game history: %s\n", game);
8169
8170     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8171     gameInfo.site = StrSave(appData.icsHost);
8172     gameInfo.date = PGNDate();
8173     gameInfo.round = StrSave("-");
8174
8175     /* Parse out names of players */
8176     while (*game == ' ') game++;
8177     p = buf;
8178     while (*game != ' ') *p++ = *game++;
8179     *p = NULLCHAR;
8180     gameInfo.white = StrSave(buf);
8181     while (*game == ' ') game++;
8182     p = buf;
8183     while (*game != ' ' && *game != '\n') *p++ = *game++;
8184     *p = NULLCHAR;
8185     gameInfo.black = StrSave(buf);
8186
8187     /* Parse moves */
8188     boardIndex = blackPlaysFirst ? 1 : 0;
8189     yynewstr(game);
8190     for (;;) {
8191         yyboardindex = boardIndex;
8192         moveType = (ChessMove) yylex();
8193         switch (moveType) {
8194           case IllegalMove:             /* maybe suicide chess, etc. */
8195   if (appData.debugMode) {
8196     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8197     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8198     setbuf(debugFP, NULL);
8199   }
8200           case WhitePromotion:
8201           case BlackPromotion:
8202           case WhiteNonPromotion:
8203           case BlackNonPromotion:
8204           case NormalMove:
8205           case WhiteCapturesEnPassant:
8206           case BlackCapturesEnPassant:
8207           case WhiteKingSideCastle:
8208           case WhiteQueenSideCastle:
8209           case BlackKingSideCastle:
8210           case BlackQueenSideCastle:
8211           case WhiteKingSideCastleWild:
8212           case WhiteQueenSideCastleWild:
8213           case BlackKingSideCastleWild:
8214           case BlackQueenSideCastleWild:
8215           /* PUSH Fabien */
8216           case WhiteHSideCastleFR:
8217           case WhiteASideCastleFR:
8218           case BlackHSideCastleFR:
8219           case BlackASideCastleFR:
8220           /* POP Fabien */
8221             fromX = currentMoveString[0] - AAA;
8222             fromY = currentMoveString[1] - ONE;
8223             toX = currentMoveString[2] - AAA;
8224             toY = currentMoveString[3] - ONE;
8225             promoChar = currentMoveString[4];
8226             break;
8227           case WhiteDrop:
8228           case BlackDrop:
8229             fromX = moveType == WhiteDrop ?
8230               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8231             (int) CharToPiece(ToLower(currentMoveString[0]));
8232             fromY = DROP_RANK;
8233             toX = currentMoveString[2] - AAA;
8234             toY = currentMoveString[3] - ONE;
8235             promoChar = NULLCHAR;
8236             break;
8237           case AmbiguousMove:
8238             /* bug? */
8239             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8240   if (appData.debugMode) {
8241     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8242     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8243     setbuf(debugFP, NULL);
8244   }
8245             DisplayError(buf, 0);
8246             return;
8247           case ImpossibleMove:
8248             /* bug? */
8249             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8250   if (appData.debugMode) {
8251     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8252     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8253     setbuf(debugFP, NULL);
8254   }
8255             DisplayError(buf, 0);
8256             return;
8257           case EndOfFile:
8258             if (boardIndex < backwardMostMove) {
8259                 /* Oops, gap.  How did that happen? */
8260                 DisplayError(_("Gap in move list"), 0);
8261                 return;
8262             }
8263             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8264             if (boardIndex > forwardMostMove) {
8265                 forwardMostMove = boardIndex;
8266             }
8267             return;
8268           case ElapsedTime:
8269             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8270                 strcat(parseList[boardIndex-1], " ");
8271                 strcat(parseList[boardIndex-1], yy_text);
8272             }
8273             continue;
8274           case Comment:
8275           case PGNTag:
8276           case NAG:
8277           default:
8278             /* ignore */
8279             continue;
8280           case WhiteWins:
8281           case BlackWins:
8282           case GameIsDrawn:
8283           case GameUnfinished:
8284             if (gameMode == IcsExamining) {
8285                 if (boardIndex < backwardMostMove) {
8286                     /* Oops, gap.  How did that happen? */
8287                     return;
8288                 }
8289                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8290                 return;
8291             }
8292             gameInfo.result = moveType;
8293             p = strchr(yy_text, '{');
8294             if (p == NULL) p = strchr(yy_text, '(');
8295             if (p == NULL) {
8296                 p = yy_text;
8297                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8298             } else {
8299                 q = strchr(p, *p == '{' ? '}' : ')');
8300                 if (q != NULL) *q = NULLCHAR;
8301                 p++;
8302             }
8303             gameInfo.resultDetails = StrSave(p);
8304             continue;
8305         }
8306         if (boardIndex >= forwardMostMove &&
8307             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8308             backwardMostMove = blackPlaysFirst ? 1 : 0;
8309             return;
8310         }
8311         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8312                                  fromY, fromX, toY, toX, promoChar,
8313                                  parseList[boardIndex]);
8314         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8315         /* currentMoveString is set as a side-effect of yylex */
8316         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8317         strcat(moveList[boardIndex], "\n");
8318         boardIndex++;
8319         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8320         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8321           case MT_NONE:
8322           case MT_STALEMATE:
8323           default:
8324             break;
8325           case MT_CHECK:
8326             if(gameInfo.variant != VariantShogi)
8327                 strcat(parseList[boardIndex - 1], "+");
8328             break;
8329           case MT_CHECKMATE:
8330           case MT_STAINMATE:
8331             strcat(parseList[boardIndex - 1], "#");
8332             break;
8333         }
8334     }
8335 }
8336
8337
8338 /* Apply a move to the given board  */
8339 void
8340 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8341      int fromX, fromY, toX, toY;
8342      int promoChar;
8343      Board board;
8344 {
8345   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8346   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8347
8348     /* [HGM] compute & store e.p. status and castling rights for new position */
8349     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8350
8351       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8352       oldEP = (signed char)board[EP_STATUS];
8353       board[EP_STATUS] = EP_NONE;
8354
8355       if( board[toY][toX] != EmptySquare )
8356            board[EP_STATUS] = EP_CAPTURE;
8357
8358   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8359   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8360        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8361
8362   if (fromY == DROP_RANK) {
8363         /* must be first */
8364         piece = board[toY][toX] = (ChessSquare) fromX;
8365   } else {
8366       int i;
8367
8368       if( board[fromY][fromX] == WhitePawn ) {
8369            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8370                board[EP_STATUS] = EP_PAWN_MOVE;
8371            if( toY-fromY==2) {
8372                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8373                         gameInfo.variant != VariantBerolina || toX < fromX)
8374                       board[EP_STATUS] = toX | berolina;
8375                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8376                         gameInfo.variant != VariantBerolina || toX > fromX)
8377                       board[EP_STATUS] = toX;
8378            }
8379       } else
8380       if( board[fromY][fromX] == BlackPawn ) {
8381            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8382                board[EP_STATUS] = EP_PAWN_MOVE;
8383            if( toY-fromY== -2) {
8384                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8385                         gameInfo.variant != VariantBerolina || toX < fromX)
8386                       board[EP_STATUS] = toX | berolina;
8387                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8388                         gameInfo.variant != VariantBerolina || toX > fromX)
8389                       board[EP_STATUS] = toX;
8390            }
8391        }
8392
8393        for(i=0; i<nrCastlingRights; i++) {
8394            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8395               board[CASTLING][i] == toX   && castlingRank[i] == toY
8396              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8397        }
8398
8399      if (fromX == toX && fromY == toY) return;
8400
8401      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8402      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8403      if(gameInfo.variant == VariantKnightmate)
8404          king += (int) WhiteUnicorn - (int) WhiteKing;
8405
8406     /* Code added by Tord: */
8407     /* FRC castling assumed when king captures friendly rook. */
8408     if (board[fromY][fromX] == WhiteKing &&
8409              board[toY][toX] == WhiteRook) {
8410       board[fromY][fromX] = EmptySquare;
8411       board[toY][toX] = EmptySquare;
8412       if(toX > fromX) {
8413         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8414       } else {
8415         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8416       }
8417     } else if (board[fromY][fromX] == BlackKing &&
8418                board[toY][toX] == BlackRook) {
8419       board[fromY][fromX] = EmptySquare;
8420       board[toY][toX] = EmptySquare;
8421       if(toX > fromX) {
8422         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8423       } else {
8424         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8425       }
8426     /* End of code added by Tord */
8427
8428     } else if (board[fromY][fromX] == king
8429         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8430         && toY == fromY && toX > fromX+1) {
8431         board[fromY][fromX] = EmptySquare;
8432         board[toY][toX] = king;
8433         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8434         board[fromY][BOARD_RGHT-1] = EmptySquare;
8435     } else if (board[fromY][fromX] == king
8436         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8437                && toY == fromY && toX < fromX-1) {
8438         board[fromY][fromX] = EmptySquare;
8439         board[toY][toX] = king;
8440         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8441         board[fromY][BOARD_LEFT] = EmptySquare;
8442     } else if (board[fromY][fromX] == WhitePawn
8443                && toY >= BOARD_HEIGHT-promoRank
8444                && gameInfo.variant != VariantXiangqi
8445                ) {
8446         /* white pawn promotion */
8447         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8448         if (board[toY][toX] == EmptySquare) {
8449             board[toY][toX] = WhiteQueen;
8450         }
8451         if(gameInfo.variant==VariantBughouse ||
8452            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8453             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8454         board[fromY][fromX] = EmptySquare;
8455     } else if ((fromY == BOARD_HEIGHT-4)
8456                && (toX != fromX)
8457                && gameInfo.variant != VariantXiangqi
8458                && gameInfo.variant != VariantBerolina
8459                && (board[fromY][fromX] == WhitePawn)
8460                && (board[toY][toX] == EmptySquare)) {
8461         board[fromY][fromX] = EmptySquare;
8462         board[toY][toX] = WhitePawn;
8463         captured = board[toY - 1][toX];
8464         board[toY - 1][toX] = EmptySquare;
8465     } else if ((fromY == BOARD_HEIGHT-4)
8466                && (toX == fromX)
8467                && gameInfo.variant == VariantBerolina
8468                && (board[fromY][fromX] == WhitePawn)
8469                && (board[toY][toX] == EmptySquare)) {
8470         board[fromY][fromX] = EmptySquare;
8471         board[toY][toX] = WhitePawn;
8472         if(oldEP & EP_BEROLIN_A) {
8473                 captured = board[fromY][fromX-1];
8474                 board[fromY][fromX-1] = EmptySquare;
8475         }else{  captured = board[fromY][fromX+1];
8476                 board[fromY][fromX+1] = EmptySquare;
8477         }
8478     } else if (board[fromY][fromX] == king
8479         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8480                && toY == fromY && toX > fromX+1) {
8481         board[fromY][fromX] = EmptySquare;
8482         board[toY][toX] = king;
8483         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8484         board[fromY][BOARD_RGHT-1] = EmptySquare;
8485     } else if (board[fromY][fromX] == king
8486         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8487                && toY == fromY && toX < fromX-1) {
8488         board[fromY][fromX] = EmptySquare;
8489         board[toY][toX] = king;
8490         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8491         board[fromY][BOARD_LEFT] = EmptySquare;
8492     } else if (fromY == 7 && fromX == 3
8493                && board[fromY][fromX] == BlackKing
8494                && toY == 7 && toX == 5) {
8495         board[fromY][fromX] = EmptySquare;
8496         board[toY][toX] = BlackKing;
8497         board[fromY][7] = EmptySquare;
8498         board[toY][4] = BlackRook;
8499     } else if (fromY == 7 && fromX == 3
8500                && board[fromY][fromX] == BlackKing
8501                && toY == 7 && toX == 1) {
8502         board[fromY][fromX] = EmptySquare;
8503         board[toY][toX] = BlackKing;
8504         board[fromY][0] = EmptySquare;
8505         board[toY][2] = BlackRook;
8506     } else if (board[fromY][fromX] == BlackPawn
8507                && toY < promoRank
8508                && gameInfo.variant != VariantXiangqi
8509                ) {
8510         /* black pawn promotion */
8511         board[toY][toX] = CharToPiece(ToLower(promoChar));
8512         if (board[toY][toX] == EmptySquare) {
8513             board[toY][toX] = BlackQueen;
8514         }
8515         if(gameInfo.variant==VariantBughouse ||
8516            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8517             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8518         board[fromY][fromX] = EmptySquare;
8519     } else if ((fromY == 3)
8520                && (toX != fromX)
8521                && gameInfo.variant != VariantXiangqi
8522                && gameInfo.variant != VariantBerolina
8523                && (board[fromY][fromX] == BlackPawn)
8524                && (board[toY][toX] == EmptySquare)) {
8525         board[fromY][fromX] = EmptySquare;
8526         board[toY][toX] = BlackPawn;
8527         captured = board[toY + 1][toX];
8528         board[toY + 1][toX] = EmptySquare;
8529     } else if ((fromY == 3)
8530                && (toX == fromX)
8531                && gameInfo.variant == VariantBerolina
8532                && (board[fromY][fromX] == BlackPawn)
8533                && (board[toY][toX] == EmptySquare)) {
8534         board[fromY][fromX] = EmptySquare;
8535         board[toY][toX] = BlackPawn;
8536         if(oldEP & EP_BEROLIN_A) {
8537                 captured = board[fromY][fromX-1];
8538                 board[fromY][fromX-1] = EmptySquare;
8539         }else{  captured = board[fromY][fromX+1];
8540                 board[fromY][fromX+1] = EmptySquare;
8541         }
8542     } else {
8543         board[toY][toX] = board[fromY][fromX];
8544         board[fromY][fromX] = EmptySquare;
8545     }
8546   }
8547
8548     if (gameInfo.holdingsWidth != 0) {
8549
8550       /* !!A lot more code needs to be written to support holdings  */
8551       /* [HGM] OK, so I have written it. Holdings are stored in the */
8552       /* penultimate board files, so they are automaticlly stored   */
8553       /* in the game history.                                       */
8554       if (fromY == DROP_RANK) {
8555         /* Delete from holdings, by decreasing count */
8556         /* and erasing image if necessary            */
8557         p = (int) fromX;
8558         if(p < (int) BlackPawn) { /* white drop */
8559              p -= (int)WhitePawn;
8560                  p = PieceToNumber((ChessSquare)p);
8561              if(p >= gameInfo.holdingsSize) p = 0;
8562              if(--board[p][BOARD_WIDTH-2] <= 0)
8563                   board[p][BOARD_WIDTH-1] = EmptySquare;
8564              if((int)board[p][BOARD_WIDTH-2] < 0)
8565                         board[p][BOARD_WIDTH-2] = 0;
8566         } else {                  /* black drop */
8567              p -= (int)BlackPawn;
8568                  p = PieceToNumber((ChessSquare)p);
8569              if(p >= gameInfo.holdingsSize) p = 0;
8570              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8571                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8572              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8573                         board[BOARD_HEIGHT-1-p][1] = 0;
8574         }
8575       }
8576       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8577           && gameInfo.variant != VariantBughouse        ) {
8578         /* [HGM] holdings: Add to holdings, if holdings exist */
8579         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8580                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8581                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8582         }
8583         p = (int) captured;
8584         if (p >= (int) BlackPawn) {
8585           p -= (int)BlackPawn;
8586           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8587                   /* in Shogi restore piece to its original  first */
8588                   captured = (ChessSquare) (DEMOTED captured);
8589                   p = DEMOTED p;
8590           }
8591           p = PieceToNumber((ChessSquare)p);
8592           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8593           board[p][BOARD_WIDTH-2]++;
8594           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8595         } else {
8596           p -= (int)WhitePawn;
8597           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8598                   captured = (ChessSquare) (DEMOTED captured);
8599                   p = DEMOTED p;
8600           }
8601           p = PieceToNumber((ChessSquare)p);
8602           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8603           board[BOARD_HEIGHT-1-p][1]++;
8604           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8605         }
8606       }
8607     } else if (gameInfo.variant == VariantAtomic) {
8608       if (captured != EmptySquare) {
8609         int y, x;
8610         for (y = toY-1; y <= toY+1; y++) {
8611           for (x = toX-1; x <= toX+1; x++) {
8612             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8613                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8614               board[y][x] = EmptySquare;
8615             }
8616           }
8617         }
8618         board[toY][toX] = EmptySquare;
8619       }
8620     }
8621     if(promoChar == '+') {
8622         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8623         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8624     } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
8625         board[toY][toX] = CharToPiece(promoChar);
8626     }
8627     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8628                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8629         // [HGM] superchess: take promotion piece out of holdings
8630         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8631         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8632             if(!--board[k][BOARD_WIDTH-2])
8633                 board[k][BOARD_WIDTH-1] = EmptySquare;
8634         } else {
8635             if(!--board[BOARD_HEIGHT-1-k][1])
8636                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8637         }
8638     }
8639
8640 }
8641
8642 /* Updates forwardMostMove */
8643 void
8644 MakeMove(fromX, fromY, toX, toY, promoChar)
8645      int fromX, fromY, toX, toY;
8646      int promoChar;
8647 {
8648 //    forwardMostMove++; // [HGM] bare: moved downstream
8649
8650     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8651         int timeLeft; static int lastLoadFlag=0; int king, piece;
8652         piece = boards[forwardMostMove][fromY][fromX];
8653         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8654         if(gameInfo.variant == VariantKnightmate)
8655             king += (int) WhiteUnicorn - (int) WhiteKing;
8656         if(forwardMostMove == 0) {
8657             if(blackPlaysFirst)
8658                 fprintf(serverMoves, "%s;", second.tidy);
8659             fprintf(serverMoves, "%s;", first.tidy);
8660             if(!blackPlaysFirst)
8661                 fprintf(serverMoves, "%s;", second.tidy);
8662         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8663         lastLoadFlag = loadFlag;
8664         // print base move
8665         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8666         // print castling suffix
8667         if( toY == fromY && piece == king ) {
8668             if(toX-fromX > 1)
8669                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8670             if(fromX-toX >1)
8671                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8672         }
8673         // e.p. suffix
8674         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8675              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8676              boards[forwardMostMove][toY][toX] == EmptySquare
8677              && fromX != toX && fromY != toY)
8678                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8679         // promotion suffix
8680         if(promoChar != NULLCHAR)
8681                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8682         if(!loadFlag) {
8683             fprintf(serverMoves, "/%d/%d",
8684                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8685             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8686             else                      timeLeft = blackTimeRemaining/1000;
8687             fprintf(serverMoves, "/%d", timeLeft);
8688         }
8689         fflush(serverMoves);
8690     }
8691
8692     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8693       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8694                         0, 1);
8695       return;
8696     }
8697     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8698     if (commentList[forwardMostMove+1] != NULL) {
8699         free(commentList[forwardMostMove+1]);
8700         commentList[forwardMostMove+1] = NULL;
8701     }
8702     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8703     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8704     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8705     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8706     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8707     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8708     gameInfo.result = GameUnfinished;
8709     if (gameInfo.resultDetails != NULL) {
8710         free(gameInfo.resultDetails);
8711         gameInfo.resultDetails = NULL;
8712     }
8713     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8714                               moveList[forwardMostMove - 1]);
8715     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8716                              PosFlags(forwardMostMove - 1),
8717                              fromY, fromX, toY, toX, promoChar,
8718                              parseList[forwardMostMove - 1]);
8719     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8720       case MT_NONE:
8721       case MT_STALEMATE:
8722       default:
8723         break;
8724       case MT_CHECK:
8725         if(gameInfo.variant != VariantShogi)
8726             strcat(parseList[forwardMostMove - 1], "+");
8727         break;
8728       case MT_CHECKMATE:
8729       case MT_STAINMATE:
8730         strcat(parseList[forwardMostMove - 1], "#");
8731         break;
8732     }
8733     if (appData.debugMode) {
8734         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8735     }
8736
8737 }
8738
8739 /* Updates currentMove if not pausing */
8740 void
8741 ShowMove(fromX, fromY, toX, toY)
8742 {
8743     int instant = (gameMode == PlayFromGameFile) ?
8744         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8745     if(appData.noGUI) return;
8746     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8747         if (!instant) {
8748             if (forwardMostMove == currentMove + 1) {
8749                 AnimateMove(boards[forwardMostMove - 1],
8750                             fromX, fromY, toX, toY);
8751             }
8752             if (appData.highlightLastMove) {
8753                 SetHighlights(fromX, fromY, toX, toY);
8754             }
8755         }
8756         currentMove = forwardMostMove;
8757     }
8758
8759     if (instant) return;
8760
8761     DisplayMove(currentMove - 1);
8762     DrawPosition(FALSE, boards[currentMove]);
8763     DisplayBothClocks();
8764     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8765 }
8766
8767 void SendEgtPath(ChessProgramState *cps)
8768 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8769         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8770
8771         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8772
8773         while(*p) {
8774             char c, *q = name+1, *r, *s;
8775
8776             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8777             while(*p && *p != ',') *q++ = *p++;
8778             *q++ = ':'; *q = 0;
8779             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8780                 strcmp(name, ",nalimov:") == 0 ) {
8781                 // take nalimov path from the menu-changeable option first, if it is defined
8782               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8783                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8784             } else
8785             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8786                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8787                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8788                 s = r = StrStr(s, ":") + 1; // beginning of path info
8789                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8790                 c = *r; *r = 0;             // temporarily null-terminate path info
8791                     *--q = 0;               // strip of trailig ':' from name
8792                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8793                 *r = c;
8794                 SendToProgram(buf,cps);     // send egtbpath command for this format
8795             }
8796             if(*p == ',') p++; // read away comma to position for next format name
8797         }
8798 }
8799
8800 void
8801 InitChessProgram(cps, setup)
8802      ChessProgramState *cps;
8803      int setup; /* [HGM] needed to setup FRC opening position */
8804 {
8805     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8806     if (appData.noChessProgram) return;
8807     hintRequested = FALSE;
8808     bookRequested = FALSE;
8809
8810     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8811     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8812     if(cps->memSize) { /* [HGM] memory */
8813       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8814         SendToProgram(buf, cps);
8815     }
8816     SendEgtPath(cps); /* [HGM] EGT */
8817     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8818       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8819         SendToProgram(buf, cps);
8820     }
8821
8822     SendToProgram(cps->initString, cps);
8823     if (gameInfo.variant != VariantNormal &&
8824         gameInfo.variant != VariantLoadable
8825         /* [HGM] also send variant if board size non-standard */
8826         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8827                                             ) {
8828       char *v = VariantName(gameInfo.variant);
8829       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8830         /* [HGM] in protocol 1 we have to assume all variants valid */
8831         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8832         DisplayFatalError(buf, 0, 1);
8833         return;
8834       }
8835
8836       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8837       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8838       if( gameInfo.variant == VariantXiangqi )
8839            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8840       if( gameInfo.variant == VariantShogi )
8841            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8842       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8843            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8844       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8845                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8846            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8847       if( gameInfo.variant == VariantCourier )
8848            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8849       if( gameInfo.variant == VariantSuper )
8850            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8851       if( gameInfo.variant == VariantGreat )
8852            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8853
8854       if(overruled) {
8855         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8856                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8857            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8858            if(StrStr(cps->variants, b) == NULL) {
8859                // specific sized variant not known, check if general sizing allowed
8860                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8861                    if(StrStr(cps->variants, "boardsize") == NULL) {
8862                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8863                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8864                        DisplayFatalError(buf, 0, 1);
8865                        return;
8866                    }
8867                    /* [HGM] here we really should compare with the maximum supported board size */
8868                }
8869            }
8870       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8871       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8872       SendToProgram(buf, cps);
8873     }
8874     currentlyInitializedVariant = gameInfo.variant;
8875
8876     /* [HGM] send opening position in FRC to first engine */
8877     if(setup) {
8878           SendToProgram("force\n", cps);
8879           SendBoard(cps, 0);
8880           /* engine is now in force mode! Set flag to wake it up after first move. */
8881           setboardSpoiledMachineBlack = 1;
8882     }
8883
8884     if (cps->sendICS) {
8885       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8886       SendToProgram(buf, cps);
8887     }
8888     cps->maybeThinking = FALSE;
8889     cps->offeredDraw = 0;
8890     if (!appData.icsActive) {
8891         SendTimeControl(cps, movesPerSession, timeControl,
8892                         timeIncrement, appData.searchDepth,
8893                         searchTime);
8894     }
8895     if (appData.showThinking
8896         // [HGM] thinking: four options require thinking output to be sent
8897         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8898                                 ) {
8899         SendToProgram("post\n", cps);
8900     }
8901     SendToProgram("hard\n", cps);
8902     if (!appData.ponderNextMove) {
8903         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8904            it without being sure what state we are in first.  "hard"
8905            is not a toggle, so that one is OK.
8906          */
8907         SendToProgram("easy\n", cps);
8908     }
8909     if (cps->usePing) {
8910       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8911       SendToProgram(buf, cps);
8912     }
8913     cps->initDone = TRUE;
8914 }
8915
8916
8917 void
8918 StartChessProgram(cps)
8919      ChessProgramState *cps;
8920 {
8921     char buf[MSG_SIZ];
8922     int err;
8923
8924     if (appData.noChessProgram) return;
8925     cps->initDone = FALSE;
8926
8927     if (strcmp(cps->host, "localhost") == 0) {
8928         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8929     } else if (*appData.remoteShell == NULLCHAR) {
8930         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8931     } else {
8932         if (*appData.remoteUser == NULLCHAR) {
8933           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8934                     cps->program);
8935         } else {
8936           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8937                     cps->host, appData.remoteUser, cps->program);
8938         }
8939         err = StartChildProcess(buf, "", &cps->pr);
8940     }
8941
8942     if (err != 0) {
8943       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8944         DisplayFatalError(buf, err, 1);
8945         cps->pr = NoProc;
8946         cps->isr = NULL;
8947         return;
8948     }
8949
8950     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8951     if (cps->protocolVersion > 1) {
8952       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8953       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8954       cps->comboCnt = 0;  //                and values of combo boxes
8955       SendToProgram(buf, cps);
8956     } else {
8957       SendToProgram("xboard\n", cps);
8958     }
8959 }
8960
8961
8962 void
8963 TwoMachinesEventIfReady P((void))
8964 {
8965   if (first.lastPing != first.lastPong) {
8966     DisplayMessage("", _("Waiting for first chess program"));
8967     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8968     return;
8969   }
8970   if (second.lastPing != second.lastPong) {
8971     DisplayMessage("", _("Waiting for second chess program"));
8972     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8973     return;
8974   }
8975   ThawUI();
8976   TwoMachinesEvent();
8977 }
8978
8979 void
8980 NextMatchGame P((void))
8981 {
8982     int index; /* [HGM] autoinc: step load index during match */
8983     Reset(FALSE, TRUE);
8984     if (*appData.loadGameFile != NULLCHAR) {
8985         index = appData.loadGameIndex;
8986         if(index < 0) { // [HGM] autoinc
8987             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8988             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8989         }
8990         LoadGameFromFile(appData.loadGameFile,
8991                          index,
8992                          appData.loadGameFile, FALSE);
8993     } else if (*appData.loadPositionFile != NULLCHAR) {
8994         index = appData.loadPositionIndex;
8995         if(index < 0) { // [HGM] autoinc
8996             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8997             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8998         }
8999         LoadPositionFromFile(appData.loadPositionFile,
9000                              index,
9001                              appData.loadPositionFile);
9002     }
9003     TwoMachinesEventIfReady();
9004 }
9005
9006 void UserAdjudicationEvent( int result )
9007 {
9008     ChessMove gameResult = GameIsDrawn;
9009
9010     if( result > 0 ) {
9011         gameResult = WhiteWins;
9012     }
9013     else if( result < 0 ) {
9014         gameResult = BlackWins;
9015     }
9016
9017     if( gameMode == TwoMachinesPlay ) {
9018         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9019     }
9020 }
9021
9022
9023 // [HGM] save: calculate checksum of game to make games easily identifiable
9024 int StringCheckSum(char *s)
9025 {
9026         int i = 0;
9027         if(s==NULL) return 0;
9028         while(*s) i = i*259 + *s++;
9029         return i;
9030 }
9031
9032 int GameCheckSum()
9033 {
9034         int i, sum=0;
9035         for(i=backwardMostMove; i<forwardMostMove; i++) {
9036                 sum += pvInfoList[i].depth;
9037                 sum += StringCheckSum(parseList[i]);
9038                 sum += StringCheckSum(commentList[i]);
9039                 sum *= 261;
9040         }
9041         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9042         return sum + StringCheckSum(commentList[i]);
9043 } // end of save patch
9044
9045 void
9046 GameEnds(result, resultDetails, whosays)
9047      ChessMove result;
9048      char *resultDetails;
9049      int whosays;
9050 {
9051     GameMode nextGameMode;
9052     int isIcsGame;
9053     char buf[MSG_SIZ], popupRequested = 0;
9054
9055     if(endingGame) return; /* [HGM] crash: forbid recursion */
9056     endingGame = 1;
9057     if(twoBoards) { // [HGM] dual: switch back to one board
9058         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9059         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9060     }
9061     if (appData.debugMode) {
9062       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9063               result, resultDetails ? resultDetails : "(null)", whosays);
9064     }
9065
9066     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9067
9068     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9069         /* If we are playing on ICS, the server decides when the
9070            game is over, but the engine can offer to draw, claim
9071            a draw, or resign.
9072          */
9073 #if ZIPPY
9074         if (appData.zippyPlay && first.initDone) {
9075             if (result == GameIsDrawn) {
9076                 /* In case draw still needs to be claimed */
9077                 SendToICS(ics_prefix);
9078                 SendToICS("draw\n");
9079             } else if (StrCaseStr(resultDetails, "resign")) {
9080                 SendToICS(ics_prefix);
9081                 SendToICS("resign\n");
9082             }
9083         }
9084 #endif
9085         endingGame = 0; /* [HGM] crash */
9086         return;
9087     }
9088
9089     /* If we're loading the game from a file, stop */
9090     if (whosays == GE_FILE) {
9091       (void) StopLoadGameTimer();
9092       gameFileFP = NULL;
9093     }
9094
9095     /* Cancel draw offers */
9096     first.offeredDraw = second.offeredDraw = 0;
9097
9098     /* If this is an ICS game, only ICS can really say it's done;
9099        if not, anyone can. */
9100     isIcsGame = (gameMode == IcsPlayingWhite ||
9101                  gameMode == IcsPlayingBlack ||
9102                  gameMode == IcsObserving    ||
9103                  gameMode == IcsExamining);
9104
9105     if (!isIcsGame || whosays == GE_ICS) {
9106         /* OK -- not an ICS game, or ICS said it was done */
9107         StopClocks();
9108         if (!isIcsGame && !appData.noChessProgram)
9109           SetUserThinkingEnables();
9110
9111         /* [HGM] if a machine claims the game end we verify this claim */
9112         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9113             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9114                 char claimer;
9115                 ChessMove trueResult = (ChessMove) -1;
9116
9117                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9118                                             first.twoMachinesColor[0] :
9119                                             second.twoMachinesColor[0] ;
9120
9121                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9122                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9123                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9124                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9125                 } else
9126                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9127                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9128                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9129                 } else
9130                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9131                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9132                 }
9133
9134                 // now verify win claims, but not in drop games, as we don't understand those yet
9135                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9136                                                  || gameInfo.variant == VariantGreat) &&
9137                     (result == WhiteWins && claimer == 'w' ||
9138                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9139                       if (appData.debugMode) {
9140                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9141                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9142                       }
9143                       if(result != trueResult) {
9144                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9145                               result = claimer == 'w' ? BlackWins : WhiteWins;
9146                               resultDetails = buf;
9147                       }
9148                 } else
9149                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9150                     && (forwardMostMove <= backwardMostMove ||
9151                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9152                         (claimer=='b')==(forwardMostMove&1))
9153                                                                                   ) {
9154                       /* [HGM] verify: draws that were not flagged are false claims */
9155                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9156                       result = claimer == 'w' ? BlackWins : WhiteWins;
9157                       resultDetails = buf;
9158                 }
9159                 /* (Claiming a loss is accepted no questions asked!) */
9160             }
9161             /* [HGM] bare: don't allow bare King to win */
9162             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9163                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9164                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9165                && result != GameIsDrawn)
9166             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9167                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9168                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9169                         if(p >= 0 && p <= (int)WhiteKing) k++;
9170                 }
9171                 if (appData.debugMode) {
9172                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9173                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9174                 }
9175                 if(k <= 1) {
9176                         result = GameIsDrawn;
9177                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9178                         resultDetails = buf;
9179                 }
9180             }
9181         }
9182
9183
9184         if(serverMoves != NULL && !loadFlag) { char c = '=';
9185             if(result==WhiteWins) c = '+';
9186             if(result==BlackWins) c = '-';
9187             if(resultDetails != NULL)
9188                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9189         }
9190         if (resultDetails != NULL) {
9191             gameInfo.result = result;
9192             gameInfo.resultDetails = StrSave(resultDetails);
9193
9194             /* display last move only if game was not loaded from file */
9195             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9196                 DisplayMove(currentMove - 1);
9197
9198             if (forwardMostMove != 0) {
9199                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9200                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9201                                                                 ) {
9202                     if (*appData.saveGameFile != NULLCHAR) {
9203                         SaveGameToFile(appData.saveGameFile, TRUE);
9204                     } else if (appData.autoSaveGames) {
9205                         AutoSaveGame();
9206                     }
9207                     if (*appData.savePositionFile != NULLCHAR) {
9208                         SavePositionToFile(appData.savePositionFile);
9209                     }
9210                 }
9211             }
9212
9213             /* Tell program how game ended in case it is learning */
9214             /* [HGM] Moved this to after saving the PGN, just in case */
9215             /* engine died and we got here through time loss. In that */
9216             /* case we will get a fatal error writing the pipe, which */
9217             /* would otherwise lose us the PGN.                       */
9218             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9219             /* output during GameEnds should never be fatal anymore   */
9220             if (gameMode == MachinePlaysWhite ||
9221                 gameMode == MachinePlaysBlack ||
9222                 gameMode == TwoMachinesPlay ||
9223                 gameMode == IcsPlayingWhite ||
9224                 gameMode == IcsPlayingBlack ||
9225                 gameMode == BeginningOfGame) {
9226                 char buf[MSG_SIZ];
9227                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9228                         resultDetails);
9229                 if (first.pr != NoProc) {
9230                     SendToProgram(buf, &first);
9231                 }
9232                 if (second.pr != NoProc &&
9233                     gameMode == TwoMachinesPlay) {
9234                     SendToProgram(buf, &second);
9235                 }
9236             }
9237         }
9238
9239         if (appData.icsActive) {
9240             if (appData.quietPlay &&
9241                 (gameMode == IcsPlayingWhite ||
9242                  gameMode == IcsPlayingBlack)) {
9243                 SendToICS(ics_prefix);
9244                 SendToICS("set shout 1\n");
9245             }
9246             nextGameMode = IcsIdle;
9247             ics_user_moved = FALSE;
9248             /* clean up premove.  It's ugly when the game has ended and the
9249              * premove highlights are still on the board.
9250              */
9251             if (gotPremove) {
9252               gotPremove = FALSE;
9253               ClearPremoveHighlights();
9254               DrawPosition(FALSE, boards[currentMove]);
9255             }
9256             if (whosays == GE_ICS) {
9257                 switch (result) {
9258                 case WhiteWins:
9259                     if (gameMode == IcsPlayingWhite)
9260                         PlayIcsWinSound();
9261                     else if(gameMode == IcsPlayingBlack)
9262                         PlayIcsLossSound();
9263                     break;
9264                 case BlackWins:
9265                     if (gameMode == IcsPlayingBlack)
9266                         PlayIcsWinSound();
9267                     else if(gameMode == IcsPlayingWhite)
9268                         PlayIcsLossSound();
9269                     break;
9270                 case GameIsDrawn:
9271                     PlayIcsDrawSound();
9272                     break;
9273                 default:
9274                     PlayIcsUnfinishedSound();
9275                 }
9276             }
9277         } else if (gameMode == EditGame ||
9278                    gameMode == PlayFromGameFile ||
9279                    gameMode == AnalyzeMode ||
9280                    gameMode == AnalyzeFile) {
9281             nextGameMode = gameMode;
9282         } else {
9283             nextGameMode = EndOfGame;
9284         }
9285         pausing = FALSE;
9286         ModeHighlight();
9287     } else {
9288         nextGameMode = gameMode;
9289     }
9290
9291     if (appData.noChessProgram) {
9292         gameMode = nextGameMode;
9293         ModeHighlight();
9294         endingGame = 0; /* [HGM] crash */
9295         return;
9296     }
9297
9298     if (first.reuse) {
9299         /* Put first chess program into idle state */
9300         if (first.pr != NoProc &&
9301             (gameMode == MachinePlaysWhite ||
9302              gameMode == MachinePlaysBlack ||
9303              gameMode == TwoMachinesPlay ||
9304              gameMode == IcsPlayingWhite ||
9305              gameMode == IcsPlayingBlack ||
9306              gameMode == BeginningOfGame)) {
9307             SendToProgram("force\n", &first);
9308             if (first.usePing) {
9309               char buf[MSG_SIZ];
9310               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9311               SendToProgram(buf, &first);
9312             }
9313         }
9314     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9315         /* Kill off first chess program */
9316         if (first.isr != NULL)
9317           RemoveInputSource(first.isr);
9318         first.isr = NULL;
9319
9320         if (first.pr != NoProc) {
9321             ExitAnalyzeMode();
9322             DoSleep( appData.delayBeforeQuit );
9323             SendToProgram("quit\n", &first);
9324             DoSleep( appData.delayAfterQuit );
9325             DestroyChildProcess(first.pr, first.useSigterm);
9326         }
9327         first.pr = NoProc;
9328     }
9329     if (second.reuse) {
9330         /* Put second chess program into idle state */
9331         if (second.pr != NoProc &&
9332             gameMode == TwoMachinesPlay) {
9333             SendToProgram("force\n", &second);
9334             if (second.usePing) {
9335               char buf[MSG_SIZ];
9336               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9337               SendToProgram(buf, &second);
9338             }
9339         }
9340     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9341         /* Kill off second chess program */
9342         if (second.isr != NULL)
9343           RemoveInputSource(second.isr);
9344         second.isr = NULL;
9345
9346         if (second.pr != NoProc) {
9347             DoSleep( appData.delayBeforeQuit );
9348             SendToProgram("quit\n", &second);
9349             DoSleep( appData.delayAfterQuit );
9350             DestroyChildProcess(second.pr, second.useSigterm);
9351         }
9352         second.pr = NoProc;
9353     }
9354
9355     if (matchMode && gameMode == TwoMachinesPlay) {
9356         switch (result) {
9357         case WhiteWins:
9358           if (first.twoMachinesColor[0] == 'w') {
9359             first.matchWins++;
9360           } else {
9361             second.matchWins++;
9362           }
9363           break;
9364         case BlackWins:
9365           if (first.twoMachinesColor[0] == 'b') {
9366             first.matchWins++;
9367           } else {
9368             second.matchWins++;
9369           }
9370           break;
9371         default:
9372           break;
9373         }
9374         if (matchGame < appData.matchGames) {
9375             char *tmp;
9376             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9377                 tmp = first.twoMachinesColor;
9378                 first.twoMachinesColor = second.twoMachinesColor;
9379                 second.twoMachinesColor = tmp;
9380             }
9381             gameMode = nextGameMode;
9382             matchGame++;
9383             if(appData.matchPause>10000 || appData.matchPause<10)
9384                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9385             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9386             endingGame = 0; /* [HGM] crash */
9387             return;
9388         } else {
9389             gameMode = nextGameMode;
9390             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9391                      first.tidy, second.tidy,
9392                      first.matchWins, second.matchWins,
9393                      appData.matchGames - (first.matchWins + second.matchWins));
9394             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9395         }
9396     }
9397     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9398         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9399       ExitAnalyzeMode();
9400     gameMode = nextGameMode;
9401     ModeHighlight();
9402     endingGame = 0;  /* [HGM] crash */
9403     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9404       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9405         matchMode = FALSE; appData.matchGames = matchGame = 0;
9406         DisplayNote(buf);
9407       }
9408     }
9409 }
9410
9411 /* Assumes program was just initialized (initString sent).
9412    Leaves program in force mode. */
9413 void
9414 FeedMovesToProgram(cps, upto)
9415      ChessProgramState *cps;
9416      int upto;
9417 {
9418     int i;
9419
9420     if (appData.debugMode)
9421       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9422               startedFromSetupPosition ? "position and " : "",
9423               backwardMostMove, upto, cps->which);
9424     if(currentlyInitializedVariant != gameInfo.variant) {
9425       char buf[MSG_SIZ];
9426         // [HGM] variantswitch: make engine aware of new variant
9427         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9428                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9429         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9430         SendToProgram(buf, cps);
9431         currentlyInitializedVariant = gameInfo.variant;
9432     }
9433     SendToProgram("force\n", cps);
9434     if (startedFromSetupPosition) {
9435         SendBoard(cps, backwardMostMove);
9436     if (appData.debugMode) {
9437         fprintf(debugFP, "feedMoves\n");
9438     }
9439     }
9440     for (i = backwardMostMove; i < upto; i++) {
9441         SendMoveToProgram(i, cps);
9442     }
9443 }
9444
9445
9446 void
9447 ResurrectChessProgram()
9448 {
9449      /* The chess program may have exited.
9450         If so, restart it and feed it all the moves made so far. */
9451
9452     if (appData.noChessProgram || first.pr != NoProc) return;
9453
9454     StartChessProgram(&first);
9455     InitChessProgram(&first, FALSE);
9456     FeedMovesToProgram(&first, currentMove);
9457
9458     if (!first.sendTime) {
9459         /* can't tell gnuchess what its clock should read,
9460            so we bow to its notion. */
9461         ResetClocks();
9462         timeRemaining[0][currentMove] = whiteTimeRemaining;
9463         timeRemaining[1][currentMove] = blackTimeRemaining;
9464     }
9465
9466     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9467                 appData.icsEngineAnalyze) && first.analysisSupport) {
9468       SendToProgram("analyze\n", &first);
9469       first.analyzing = TRUE;
9470     }
9471 }
9472
9473 /*
9474  * Button procedures
9475  */
9476 void
9477 Reset(redraw, init)
9478      int redraw, init;
9479 {
9480     int i;
9481
9482     if (appData.debugMode) {
9483         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9484                 redraw, init, gameMode);
9485     }
9486     CleanupTail(); // [HGM] vari: delete any stored variations
9487     pausing = pauseExamInvalid = FALSE;
9488     startedFromSetupPosition = blackPlaysFirst = FALSE;
9489     firstMove = TRUE;
9490     whiteFlag = blackFlag = FALSE;
9491     userOfferedDraw = FALSE;
9492     hintRequested = bookRequested = FALSE;
9493     first.maybeThinking = FALSE;
9494     second.maybeThinking = FALSE;
9495     first.bookSuspend = FALSE; // [HGM] book
9496     second.bookSuspend = FALSE;
9497     thinkOutput[0] = NULLCHAR;
9498     lastHint[0] = NULLCHAR;
9499     ClearGameInfo(&gameInfo);
9500     gameInfo.variant = StringToVariant(appData.variant);
9501     ics_user_moved = ics_clock_paused = FALSE;
9502     ics_getting_history = H_FALSE;
9503     ics_gamenum = -1;
9504     white_holding[0] = black_holding[0] = NULLCHAR;
9505     ClearProgramStats();
9506     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9507
9508     ResetFrontEnd();
9509     ClearHighlights();
9510     flipView = appData.flipView;
9511     ClearPremoveHighlights();
9512     gotPremove = FALSE;
9513     alarmSounded = FALSE;
9514
9515     GameEnds(EndOfFile, NULL, GE_PLAYER);
9516     if(appData.serverMovesName != NULL) {
9517         /* [HGM] prepare to make moves file for broadcasting */
9518         clock_t t = clock();
9519         if(serverMoves != NULL) fclose(serverMoves);
9520         serverMoves = fopen(appData.serverMovesName, "r");
9521         if(serverMoves != NULL) {
9522             fclose(serverMoves);
9523             /* delay 15 sec before overwriting, so all clients can see end */
9524             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9525         }
9526         serverMoves = fopen(appData.serverMovesName, "w");
9527     }
9528
9529     ExitAnalyzeMode();
9530     gameMode = BeginningOfGame;
9531     ModeHighlight();
9532     if(appData.icsActive) gameInfo.variant = VariantNormal;
9533     currentMove = forwardMostMove = backwardMostMove = 0;
9534     InitPosition(redraw);
9535     for (i = 0; i < MAX_MOVES; i++) {
9536         if (commentList[i] != NULL) {
9537             free(commentList[i]);
9538             commentList[i] = NULL;
9539         }
9540     }
9541     ResetClocks();
9542     timeRemaining[0][0] = whiteTimeRemaining;
9543     timeRemaining[1][0] = blackTimeRemaining;
9544     if (first.pr == NULL) {
9545         StartChessProgram(&first);
9546     }
9547     if (init) {
9548             InitChessProgram(&first, startedFromSetupPosition);
9549     }
9550     DisplayTitle("");
9551     DisplayMessage("", "");
9552     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9553     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9554 }
9555
9556 void
9557 AutoPlayGameLoop()
9558 {
9559     for (;;) {
9560         if (!AutoPlayOneMove())
9561           return;
9562         if (matchMode || appData.timeDelay == 0)
9563           continue;
9564         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9565           return;
9566         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9567         break;
9568     }
9569 }
9570
9571
9572 int
9573 AutoPlayOneMove()
9574 {
9575     int fromX, fromY, toX, toY;
9576
9577     if (appData.debugMode) {
9578       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9579     }
9580
9581     if (gameMode != PlayFromGameFile)
9582       return FALSE;
9583
9584     if (currentMove >= forwardMostMove) {
9585       gameMode = EditGame;
9586       ModeHighlight();
9587
9588       /* [AS] Clear current move marker at the end of a game */
9589       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9590
9591       return FALSE;
9592     }
9593
9594     toX = moveList[currentMove][2] - AAA;
9595     toY = moveList[currentMove][3] - ONE;
9596
9597     if (moveList[currentMove][1] == '@') {
9598         if (appData.highlightLastMove) {
9599             SetHighlights(-1, -1, toX, toY);
9600         }
9601     } else {
9602         fromX = moveList[currentMove][0] - AAA;
9603         fromY = moveList[currentMove][1] - ONE;
9604
9605         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9606
9607         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9608
9609         if (appData.highlightLastMove) {
9610             SetHighlights(fromX, fromY, toX, toY);
9611         }
9612     }
9613     DisplayMove(currentMove);
9614     SendMoveToProgram(currentMove++, &first);
9615     DisplayBothClocks();
9616     DrawPosition(FALSE, boards[currentMove]);
9617     // [HGM] PV info: always display, routine tests if empty
9618     DisplayComment(currentMove - 1, commentList[currentMove]);
9619     return TRUE;
9620 }
9621
9622
9623 int
9624 LoadGameOneMove(readAhead)
9625      ChessMove readAhead;
9626 {
9627     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9628     char promoChar = NULLCHAR;
9629     ChessMove moveType;
9630     char move[MSG_SIZ];
9631     char *p, *q;
9632
9633     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9634         gameMode != AnalyzeMode && gameMode != Training) {
9635         gameFileFP = NULL;
9636         return FALSE;
9637     }
9638
9639     yyboardindex = forwardMostMove;
9640     if (readAhead != EndOfFile) {
9641       moveType = readAhead;
9642     } else {
9643       if (gameFileFP == NULL)
9644           return FALSE;
9645       moveType = (ChessMove) yylex();
9646     }
9647
9648     done = FALSE;
9649     switch (moveType) {
9650       case Comment:
9651         if (appData.debugMode)
9652           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9653         p = yy_text;
9654
9655         /* append the comment but don't display it */
9656         AppendComment(currentMove, p, FALSE);
9657         return TRUE;
9658
9659       case WhiteCapturesEnPassant:
9660       case BlackCapturesEnPassant:
9661       case WhitePromotion:
9662       case BlackPromotion:
9663       case WhiteNonPromotion:
9664       case BlackNonPromotion:
9665       case NormalMove:
9666       case WhiteKingSideCastle:
9667       case WhiteQueenSideCastle:
9668       case BlackKingSideCastle:
9669       case BlackQueenSideCastle:
9670       case WhiteKingSideCastleWild:
9671       case WhiteQueenSideCastleWild:
9672       case BlackKingSideCastleWild:
9673       case BlackQueenSideCastleWild:
9674       /* PUSH Fabien */
9675       case WhiteHSideCastleFR:
9676       case WhiteASideCastleFR:
9677       case BlackHSideCastleFR:
9678       case BlackASideCastleFR:
9679       /* POP Fabien */
9680         if (appData.debugMode)
9681           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9682         fromX = currentMoveString[0] - AAA;
9683         fromY = currentMoveString[1] - ONE;
9684         toX = currentMoveString[2] - AAA;
9685         toY = currentMoveString[3] - ONE;
9686         promoChar = currentMoveString[4];
9687         break;
9688
9689       case WhiteDrop:
9690       case BlackDrop:
9691         if (appData.debugMode)
9692           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9693         fromX = moveType == WhiteDrop ?
9694           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9695         (int) CharToPiece(ToLower(currentMoveString[0]));
9696         fromY = DROP_RANK;
9697         toX = currentMoveString[2] - AAA;
9698         toY = currentMoveString[3] - ONE;
9699         break;
9700
9701       case WhiteWins:
9702       case BlackWins:
9703       case GameIsDrawn:
9704       case GameUnfinished:
9705         if (appData.debugMode)
9706           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9707         p = strchr(yy_text, '{');
9708         if (p == NULL) p = strchr(yy_text, '(');
9709         if (p == NULL) {
9710             p = yy_text;
9711             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9712         } else {
9713             q = strchr(p, *p == '{' ? '}' : ')');
9714             if (q != NULL) *q = NULLCHAR;
9715             p++;
9716         }
9717         GameEnds(moveType, p, GE_FILE);
9718         done = TRUE;
9719         if (cmailMsgLoaded) {
9720             ClearHighlights();
9721             flipView = WhiteOnMove(currentMove);
9722             if (moveType == GameUnfinished) flipView = !flipView;
9723             if (appData.debugMode)
9724               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9725         }
9726         break;
9727
9728       case EndOfFile:
9729         if (appData.debugMode)
9730           fprintf(debugFP, "Parser hit end of file\n");
9731         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9732           case MT_NONE:
9733           case MT_CHECK:
9734             break;
9735           case MT_CHECKMATE:
9736           case MT_STAINMATE:
9737             if (WhiteOnMove(currentMove)) {
9738                 GameEnds(BlackWins, "Black mates", GE_FILE);
9739             } else {
9740                 GameEnds(WhiteWins, "White mates", GE_FILE);
9741             }
9742             break;
9743           case MT_STALEMATE:
9744             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9745             break;
9746         }
9747         done = TRUE;
9748         break;
9749
9750       case MoveNumberOne:
9751         if (lastLoadGameStart == GNUChessGame) {
9752             /* GNUChessGames have numbers, but they aren't move numbers */
9753             if (appData.debugMode)
9754               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9755                       yy_text, (int) moveType);
9756             return LoadGameOneMove(EndOfFile); /* tail recursion */
9757         }
9758         /* else fall thru */
9759
9760       case XBoardGame:
9761       case GNUChessGame:
9762       case PGNTag:
9763         /* Reached start of next game in file */
9764         if (appData.debugMode)
9765           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9766         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9767           case MT_NONE:
9768           case MT_CHECK:
9769             break;
9770           case MT_CHECKMATE:
9771           case MT_STAINMATE:
9772             if (WhiteOnMove(currentMove)) {
9773                 GameEnds(BlackWins, "Black mates", GE_FILE);
9774             } else {
9775                 GameEnds(WhiteWins, "White mates", GE_FILE);
9776             }
9777             break;
9778           case MT_STALEMATE:
9779             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9780             break;
9781         }
9782         done = TRUE;
9783         break;
9784
9785       case PositionDiagram:     /* should not happen; ignore */
9786       case ElapsedTime:         /* ignore */
9787       case NAG:                 /* ignore */
9788         if (appData.debugMode)
9789           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9790                   yy_text, (int) moveType);
9791         return LoadGameOneMove(EndOfFile); /* tail recursion */
9792
9793       case IllegalMove:
9794         if (appData.testLegality) {
9795             if (appData.debugMode)
9796               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9797             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9798                     (forwardMostMove / 2) + 1,
9799                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9800             DisplayError(move, 0);
9801             done = TRUE;
9802         } else {
9803             if (appData.debugMode)
9804               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9805                       yy_text, currentMoveString);
9806             fromX = currentMoveString[0] - AAA;
9807             fromY = currentMoveString[1] - ONE;
9808             toX = currentMoveString[2] - AAA;
9809             toY = currentMoveString[3] - ONE;
9810             promoChar = currentMoveString[4];
9811         }
9812         break;
9813
9814       case AmbiguousMove:
9815         if (appData.debugMode)
9816           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9817         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9818                 (forwardMostMove / 2) + 1,
9819                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9820         DisplayError(move, 0);
9821         done = TRUE;
9822         break;
9823
9824       default:
9825       case ImpossibleMove:
9826         if (appData.debugMode)
9827           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9828         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9829                 (forwardMostMove / 2) + 1,
9830                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9831         DisplayError(move, 0);
9832         done = TRUE;
9833         break;
9834     }
9835
9836     if (done) {
9837         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9838             DrawPosition(FALSE, boards[currentMove]);
9839             DisplayBothClocks();
9840             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9841               DisplayComment(currentMove - 1, commentList[currentMove]);
9842         }
9843         (void) StopLoadGameTimer();
9844         gameFileFP = NULL;
9845         cmailOldMove = forwardMostMove;
9846         return FALSE;
9847     } else {
9848         /* currentMoveString is set as a side-effect of yylex */
9849         strcat(currentMoveString, "\n");
9850         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9851
9852         thinkOutput[0] = NULLCHAR;
9853         MakeMove(fromX, fromY, toX, toY, promoChar);
9854         currentMove = forwardMostMove;
9855         return TRUE;
9856     }
9857 }
9858
9859 /* Load the nth game from the given file */
9860 int
9861 LoadGameFromFile(filename, n, title, useList)
9862      char *filename;
9863      int n;
9864      char *title;
9865      /*Boolean*/ int useList;
9866 {
9867     FILE *f;
9868     char buf[MSG_SIZ];
9869
9870     if (strcmp(filename, "-") == 0) {
9871         f = stdin;
9872         title = "stdin";
9873     } else {
9874         f = fopen(filename, "rb");
9875         if (f == NULL) {
9876           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9877             DisplayError(buf, errno);
9878             return FALSE;
9879         }
9880     }
9881     if (fseek(f, 0, 0) == -1) {
9882         /* f is not seekable; probably a pipe */
9883         useList = FALSE;
9884     }
9885     if (useList && n == 0) {
9886         int error = GameListBuild(f);
9887         if (error) {
9888             DisplayError(_("Cannot build game list"), error);
9889         } else if (!ListEmpty(&gameList) &&
9890                    ((ListGame *) gameList.tailPred)->number > 1) {
9891             GameListPopUp(f, title);
9892             return TRUE;
9893         }
9894         GameListDestroy();
9895         n = 1;
9896     }
9897     if (n == 0) n = 1;
9898     return LoadGame(f, n, title, FALSE);
9899 }
9900
9901
9902 void
9903 MakeRegisteredMove()
9904 {
9905     int fromX, fromY, toX, toY;
9906     char promoChar;
9907     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9908         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9909           case CMAIL_MOVE:
9910           case CMAIL_DRAW:
9911             if (appData.debugMode)
9912               fprintf(debugFP, "Restoring %s for game %d\n",
9913                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9914
9915             thinkOutput[0] = NULLCHAR;
9916             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9917             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9918             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9919             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9920             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9921             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9922             MakeMove(fromX, fromY, toX, toY, promoChar);
9923             ShowMove(fromX, fromY, toX, toY);
9924
9925             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9926               case MT_NONE:
9927               case MT_CHECK:
9928                 break;
9929
9930               case MT_CHECKMATE:
9931               case MT_STAINMATE:
9932                 if (WhiteOnMove(currentMove)) {
9933                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9934                 } else {
9935                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9936                 }
9937                 break;
9938
9939               case MT_STALEMATE:
9940                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9941                 break;
9942             }
9943
9944             break;
9945
9946           case CMAIL_RESIGN:
9947             if (WhiteOnMove(currentMove)) {
9948                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9949             } else {
9950                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9951             }
9952             break;
9953
9954           case CMAIL_ACCEPT:
9955             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9956             break;
9957
9958           default:
9959             break;
9960         }
9961     }
9962
9963     return;
9964 }
9965
9966 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9967 int
9968 CmailLoadGame(f, gameNumber, title, useList)
9969      FILE *f;
9970      int gameNumber;
9971      char *title;
9972      int useList;
9973 {
9974     int retVal;
9975
9976     if (gameNumber > nCmailGames) {
9977         DisplayError(_("No more games in this message"), 0);
9978         return FALSE;
9979     }
9980     if (f == lastLoadGameFP) {
9981         int offset = gameNumber - lastLoadGameNumber;
9982         if (offset == 0) {
9983             cmailMsg[0] = NULLCHAR;
9984             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9985                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9986                 nCmailMovesRegistered--;
9987             }
9988             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9989             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9990                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9991             }
9992         } else {
9993             if (! RegisterMove()) return FALSE;
9994         }
9995     }
9996
9997     retVal = LoadGame(f, gameNumber, title, useList);
9998
9999     /* Make move registered during previous look at this game, if any */
10000     MakeRegisteredMove();
10001
10002     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10003         commentList[currentMove]
10004           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10005         DisplayComment(currentMove - 1, commentList[currentMove]);
10006     }
10007
10008     return retVal;
10009 }
10010
10011 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10012 int
10013 ReloadGame(offset)
10014      int offset;
10015 {
10016     int gameNumber = lastLoadGameNumber + offset;
10017     if (lastLoadGameFP == NULL) {
10018         DisplayError(_("No game has been loaded yet"), 0);
10019         return FALSE;
10020     }
10021     if (gameNumber <= 0) {
10022         DisplayError(_("Can't back up any further"), 0);
10023         return FALSE;
10024     }
10025     if (cmailMsgLoaded) {
10026         return CmailLoadGame(lastLoadGameFP, gameNumber,
10027                              lastLoadGameTitle, lastLoadGameUseList);
10028     } else {
10029         return LoadGame(lastLoadGameFP, gameNumber,
10030                         lastLoadGameTitle, lastLoadGameUseList);
10031     }
10032 }
10033
10034
10035
10036 /* Load the nth game from open file f */
10037 int
10038 LoadGame(f, gameNumber, title, useList)
10039      FILE *f;
10040      int gameNumber;
10041      char *title;
10042      int useList;
10043 {
10044     ChessMove cm;
10045     char buf[MSG_SIZ];
10046     int gn = gameNumber;
10047     ListGame *lg = NULL;
10048     int numPGNTags = 0;
10049     int err;
10050     GameMode oldGameMode;
10051     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10052
10053     if (appData.debugMode)
10054         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10055
10056     if (gameMode == Training )
10057         SetTrainingModeOff();
10058
10059     oldGameMode = gameMode;
10060     if (gameMode != BeginningOfGame) {
10061       Reset(FALSE, TRUE);
10062     }
10063
10064     gameFileFP = f;
10065     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10066         fclose(lastLoadGameFP);
10067     }
10068
10069     if (useList) {
10070         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10071
10072         if (lg) {
10073             fseek(f, lg->offset, 0);
10074             GameListHighlight(gameNumber);
10075             gn = 1;
10076         }
10077         else {
10078             DisplayError(_("Game number out of range"), 0);
10079             return FALSE;
10080         }
10081     } else {
10082         GameListDestroy();
10083         if (fseek(f, 0, 0) == -1) {
10084             if (f == lastLoadGameFP ?
10085                 gameNumber == lastLoadGameNumber + 1 :
10086                 gameNumber == 1) {
10087                 gn = 1;
10088             } else {
10089                 DisplayError(_("Can't seek on game file"), 0);
10090                 return FALSE;
10091             }
10092         }
10093     }
10094     lastLoadGameFP = f;
10095     lastLoadGameNumber = gameNumber;
10096     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10097     lastLoadGameUseList = useList;
10098
10099     yynewfile(f);
10100
10101     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10102       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10103                 lg->gameInfo.black);
10104             DisplayTitle(buf);
10105     } else if (*title != NULLCHAR) {
10106         if (gameNumber > 1) {
10107           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10108             DisplayTitle(buf);
10109         } else {
10110             DisplayTitle(title);
10111         }
10112     }
10113
10114     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10115         gameMode = PlayFromGameFile;
10116         ModeHighlight();
10117     }
10118
10119     currentMove = forwardMostMove = backwardMostMove = 0;
10120     CopyBoard(boards[0], initialPosition);
10121     StopClocks();
10122
10123     /*
10124      * Skip the first gn-1 games in the file.
10125      * Also skip over anything that precedes an identifiable
10126      * start of game marker, to avoid being confused by
10127      * garbage at the start of the file.  Currently
10128      * recognized start of game markers are the move number "1",
10129      * the pattern "gnuchess .* game", the pattern
10130      * "^[#;%] [^ ]* game file", and a PGN tag block.
10131      * A game that starts with one of the latter two patterns
10132      * will also have a move number 1, possibly
10133      * following a position diagram.
10134      * 5-4-02: Let's try being more lenient and allowing a game to
10135      * start with an unnumbered move.  Does that break anything?
10136      */
10137     cm = lastLoadGameStart = EndOfFile;
10138     while (gn > 0) {
10139         yyboardindex = forwardMostMove;
10140         cm = (ChessMove) yylex();
10141         switch (cm) {
10142           case EndOfFile:
10143             if (cmailMsgLoaded) {
10144                 nCmailGames = CMAIL_MAX_GAMES - gn;
10145             } else {
10146                 Reset(TRUE, TRUE);
10147                 DisplayError(_("Game not found in file"), 0);
10148             }
10149             return FALSE;
10150
10151           case GNUChessGame:
10152           case XBoardGame:
10153             gn--;
10154             lastLoadGameStart = cm;
10155             break;
10156
10157           case MoveNumberOne:
10158             switch (lastLoadGameStart) {
10159               case GNUChessGame:
10160               case XBoardGame:
10161               case PGNTag:
10162                 break;
10163               case MoveNumberOne:
10164               case EndOfFile:
10165                 gn--;           /* count this game */
10166                 lastLoadGameStart = cm;
10167                 break;
10168               default:
10169                 /* impossible */
10170                 break;
10171             }
10172             break;
10173
10174           case PGNTag:
10175             switch (lastLoadGameStart) {
10176               case GNUChessGame:
10177               case PGNTag:
10178               case MoveNumberOne:
10179               case EndOfFile:
10180                 gn--;           /* count this game */
10181                 lastLoadGameStart = cm;
10182                 break;
10183               case XBoardGame:
10184                 lastLoadGameStart = cm; /* game counted already */
10185                 break;
10186               default:
10187                 /* impossible */
10188                 break;
10189             }
10190             if (gn > 0) {
10191                 do {
10192                     yyboardindex = forwardMostMove;
10193                     cm = (ChessMove) yylex();
10194                 } while (cm == PGNTag || cm == Comment);
10195             }
10196             break;
10197
10198           case WhiteWins:
10199           case BlackWins:
10200           case GameIsDrawn:
10201             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10202                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10203                     != CMAIL_OLD_RESULT) {
10204                     nCmailResults ++ ;
10205                     cmailResult[  CMAIL_MAX_GAMES
10206                                 - gn - 1] = CMAIL_OLD_RESULT;
10207                 }
10208             }
10209             break;
10210
10211           case NormalMove:
10212             /* Only a NormalMove can be at the start of a game
10213              * without a position diagram. */
10214             if (lastLoadGameStart == EndOfFile ) {
10215               gn--;
10216               lastLoadGameStart = MoveNumberOne;
10217             }
10218             break;
10219
10220           default:
10221             break;
10222         }
10223     }
10224
10225     if (appData.debugMode)
10226       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10227
10228     if (cm == XBoardGame) {
10229         /* Skip any header junk before position diagram and/or move 1 */
10230         for (;;) {
10231             yyboardindex = forwardMostMove;
10232             cm = (ChessMove) yylex();
10233
10234             if (cm == EndOfFile ||
10235                 cm == GNUChessGame || cm == XBoardGame) {
10236                 /* Empty game; pretend end-of-file and handle later */
10237                 cm = EndOfFile;
10238                 break;
10239             }
10240
10241             if (cm == MoveNumberOne || cm == PositionDiagram ||
10242                 cm == PGNTag || cm == Comment)
10243               break;
10244         }
10245     } else if (cm == GNUChessGame) {
10246         if (gameInfo.event != NULL) {
10247             free(gameInfo.event);
10248         }
10249         gameInfo.event = StrSave(yy_text);
10250     }
10251
10252     startedFromSetupPosition = FALSE;
10253     while (cm == PGNTag) {
10254         if (appData.debugMode)
10255           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10256         err = ParsePGNTag(yy_text, &gameInfo);
10257         if (!err) numPGNTags++;
10258
10259         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10260         if(gameInfo.variant != oldVariant) {
10261             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10262             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10263             InitPosition(TRUE);
10264             oldVariant = gameInfo.variant;
10265             if (appData.debugMode)
10266               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10267         }
10268
10269
10270         if (gameInfo.fen != NULL) {
10271           Board initial_position;
10272           startedFromSetupPosition = TRUE;
10273           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10274             Reset(TRUE, TRUE);
10275             DisplayError(_("Bad FEN position in file"), 0);
10276             return FALSE;
10277           }
10278           CopyBoard(boards[0], initial_position);
10279           if (blackPlaysFirst) {
10280             currentMove = forwardMostMove = backwardMostMove = 1;
10281             CopyBoard(boards[1], initial_position);
10282             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10283             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10284             timeRemaining[0][1] = whiteTimeRemaining;
10285             timeRemaining[1][1] = blackTimeRemaining;
10286             if (commentList[0] != NULL) {
10287               commentList[1] = commentList[0];
10288               commentList[0] = NULL;
10289             }
10290           } else {
10291             currentMove = forwardMostMove = backwardMostMove = 0;
10292           }
10293           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10294           {   int i;
10295               initialRulePlies = FENrulePlies;
10296               for( i=0; i< nrCastlingRights; i++ )
10297                   initialRights[i] = initial_position[CASTLING][i];
10298           }
10299           yyboardindex = forwardMostMove;
10300           free(gameInfo.fen);
10301           gameInfo.fen = NULL;
10302         }
10303
10304         yyboardindex = forwardMostMove;
10305         cm = (ChessMove) yylex();
10306
10307         /* Handle comments interspersed among the tags */
10308         while (cm == Comment) {
10309             char *p;
10310             if (appData.debugMode)
10311               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10312             p = yy_text;
10313             AppendComment(currentMove, p, FALSE);
10314             yyboardindex = forwardMostMove;
10315             cm = (ChessMove) yylex();
10316         }
10317     }
10318
10319     /* don't rely on existence of Event tag since if game was
10320      * pasted from clipboard the Event tag may not exist
10321      */
10322     if (numPGNTags > 0){
10323         char *tags;
10324         if (gameInfo.variant == VariantNormal) {
10325           VariantClass v = StringToVariant(gameInfo.event);
10326           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10327           if(v < VariantShogi) gameInfo.variant = v;
10328         }
10329         if (!matchMode) {
10330           if( appData.autoDisplayTags ) {
10331             tags = PGNTags(&gameInfo);
10332             TagsPopUp(tags, CmailMsg());
10333             free(tags);
10334           }
10335         }
10336     } else {
10337         /* Make something up, but don't display it now */
10338         SetGameInfo();
10339         TagsPopDown();
10340     }
10341
10342     if (cm == PositionDiagram) {
10343         int i, j;
10344         char *p;
10345         Board initial_position;
10346
10347         if (appData.debugMode)
10348           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10349
10350         if (!startedFromSetupPosition) {
10351             p = yy_text;
10352             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10353               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10354                 switch (*p) {
10355                   case '[':
10356                   case '-':
10357                   case ' ':
10358                   case '\t':
10359                   case '\n':
10360                   case '\r':
10361                     break;
10362                   default:
10363                     initial_position[i][j++] = CharToPiece(*p);
10364                     break;
10365                 }
10366             while (*p == ' ' || *p == '\t' ||
10367                    *p == '\n' || *p == '\r') p++;
10368
10369             if (strncmp(p, "black", strlen("black"))==0)
10370               blackPlaysFirst = TRUE;
10371             else
10372               blackPlaysFirst = FALSE;
10373             startedFromSetupPosition = TRUE;
10374
10375             CopyBoard(boards[0], initial_position);
10376             if (blackPlaysFirst) {
10377                 currentMove = forwardMostMove = backwardMostMove = 1;
10378                 CopyBoard(boards[1], initial_position);
10379                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10380                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10381                 timeRemaining[0][1] = whiteTimeRemaining;
10382                 timeRemaining[1][1] = blackTimeRemaining;
10383                 if (commentList[0] != NULL) {
10384                     commentList[1] = commentList[0];
10385                     commentList[0] = NULL;
10386                 }
10387             } else {
10388                 currentMove = forwardMostMove = backwardMostMove = 0;
10389             }
10390         }
10391         yyboardindex = forwardMostMove;
10392         cm = (ChessMove) yylex();
10393     }
10394
10395     if (first.pr == NoProc) {
10396         StartChessProgram(&first);
10397     }
10398     InitChessProgram(&first, FALSE);
10399     SendToProgram("force\n", &first);
10400     if (startedFromSetupPosition) {
10401         SendBoard(&first, forwardMostMove);
10402     if (appData.debugMode) {
10403         fprintf(debugFP, "Load Game\n");
10404     }
10405         DisplayBothClocks();
10406     }
10407
10408     /* [HGM] server: flag to write setup moves in broadcast file as one */
10409     loadFlag = appData.suppressLoadMoves;
10410
10411     while (cm == Comment) {
10412         char *p;
10413         if (appData.debugMode)
10414           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10415         p = yy_text;
10416         AppendComment(currentMove, p, FALSE);
10417         yyboardindex = forwardMostMove;
10418         cm = (ChessMove) yylex();
10419     }
10420
10421     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10422         cm == WhiteWins || cm == BlackWins ||
10423         cm == GameIsDrawn || cm == GameUnfinished) {
10424         DisplayMessage("", _("No moves in game"));
10425         if (cmailMsgLoaded) {
10426             if (appData.debugMode)
10427               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10428             ClearHighlights();
10429             flipView = FALSE;
10430         }
10431         DrawPosition(FALSE, boards[currentMove]);
10432         DisplayBothClocks();
10433         gameMode = EditGame;
10434         ModeHighlight();
10435         gameFileFP = NULL;
10436         cmailOldMove = 0;
10437         return TRUE;
10438     }
10439
10440     // [HGM] PV info: routine tests if comment empty
10441     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10442         DisplayComment(currentMove - 1, commentList[currentMove]);
10443     }
10444     if (!matchMode && appData.timeDelay != 0)
10445       DrawPosition(FALSE, boards[currentMove]);
10446
10447     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10448       programStats.ok_to_send = 1;
10449     }
10450
10451     /* if the first token after the PGN tags is a move
10452      * and not move number 1, retrieve it from the parser
10453      */
10454     if (cm != MoveNumberOne)
10455         LoadGameOneMove(cm);
10456
10457     /* load the remaining moves from the file */
10458     while (LoadGameOneMove(EndOfFile)) {
10459       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10460       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10461     }
10462
10463     /* rewind to the start of the game */
10464     currentMove = backwardMostMove;
10465
10466     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10467
10468     if (oldGameMode == AnalyzeFile ||
10469         oldGameMode == AnalyzeMode) {
10470       AnalyzeFileEvent();
10471     }
10472
10473     if (matchMode || appData.timeDelay == 0) {
10474       ToEndEvent();
10475       gameMode = EditGame;
10476       ModeHighlight();
10477     } else if (appData.timeDelay > 0) {
10478       AutoPlayGameLoop();
10479     }
10480
10481     if (appData.debugMode)
10482         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10483
10484     loadFlag = 0; /* [HGM] true game starts */
10485     return TRUE;
10486 }
10487
10488 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10489 int
10490 ReloadPosition(offset)
10491      int offset;
10492 {
10493     int positionNumber = lastLoadPositionNumber + offset;
10494     if (lastLoadPositionFP == NULL) {
10495         DisplayError(_("No position has been loaded yet"), 0);
10496         return FALSE;
10497     }
10498     if (positionNumber <= 0) {
10499         DisplayError(_("Can't back up any further"), 0);
10500         return FALSE;
10501     }
10502     return LoadPosition(lastLoadPositionFP, positionNumber,
10503                         lastLoadPositionTitle);
10504 }
10505
10506 /* Load the nth position from the given file */
10507 int
10508 LoadPositionFromFile(filename, n, title)
10509      char *filename;
10510      int n;
10511      char *title;
10512 {
10513     FILE *f;
10514     char buf[MSG_SIZ];
10515
10516     if (strcmp(filename, "-") == 0) {
10517         return LoadPosition(stdin, n, "stdin");
10518     } else {
10519         f = fopen(filename, "rb");
10520         if (f == NULL) {
10521             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10522             DisplayError(buf, errno);
10523             return FALSE;
10524         } else {
10525             return LoadPosition(f, n, title);
10526         }
10527     }
10528 }
10529
10530 /* Load the nth position from the given open file, and close it */
10531 int
10532 LoadPosition(f, positionNumber, title)
10533      FILE *f;
10534      int positionNumber;
10535      char *title;
10536 {
10537     char *p, line[MSG_SIZ];
10538     Board initial_position;
10539     int i, j, fenMode, pn;
10540
10541     if (gameMode == Training )
10542         SetTrainingModeOff();
10543
10544     if (gameMode != BeginningOfGame) {
10545         Reset(FALSE, TRUE);
10546     }
10547     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10548         fclose(lastLoadPositionFP);
10549     }
10550     if (positionNumber == 0) positionNumber = 1;
10551     lastLoadPositionFP = f;
10552     lastLoadPositionNumber = positionNumber;
10553     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10554     if (first.pr == NoProc) {
10555       StartChessProgram(&first);
10556       InitChessProgram(&first, FALSE);
10557     }
10558     pn = positionNumber;
10559     if (positionNumber < 0) {
10560         /* Negative position number means to seek to that byte offset */
10561         if (fseek(f, -positionNumber, 0) == -1) {
10562             DisplayError(_("Can't seek on position file"), 0);
10563             return FALSE;
10564         };
10565         pn = 1;
10566     } else {
10567         if (fseek(f, 0, 0) == -1) {
10568             if (f == lastLoadPositionFP ?
10569                 positionNumber == lastLoadPositionNumber + 1 :
10570                 positionNumber == 1) {
10571                 pn = 1;
10572             } else {
10573                 DisplayError(_("Can't seek on position file"), 0);
10574                 return FALSE;
10575             }
10576         }
10577     }
10578     /* See if this file is FEN or old-style xboard */
10579     if (fgets(line, MSG_SIZ, f) == NULL) {
10580         DisplayError(_("Position not found in file"), 0);
10581         return FALSE;
10582     }
10583     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10584     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10585
10586     if (pn >= 2) {
10587         if (fenMode || line[0] == '#') pn--;
10588         while (pn > 0) {
10589             /* skip positions before number pn */
10590             if (fgets(line, MSG_SIZ, f) == NULL) {
10591                 Reset(TRUE, TRUE);
10592                 DisplayError(_("Position not found in file"), 0);
10593                 return FALSE;
10594             }
10595             if (fenMode || line[0] == '#') pn--;
10596         }
10597     }
10598
10599     if (fenMode) {
10600         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10601             DisplayError(_("Bad FEN position in file"), 0);
10602             return FALSE;
10603         }
10604     } else {
10605         (void) fgets(line, MSG_SIZ, f);
10606         (void) fgets(line, MSG_SIZ, f);
10607
10608         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10609             (void) fgets(line, MSG_SIZ, f);
10610             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10611                 if (*p == ' ')
10612                   continue;
10613                 initial_position[i][j++] = CharToPiece(*p);
10614             }
10615         }
10616
10617         blackPlaysFirst = FALSE;
10618         if (!feof(f)) {
10619             (void) fgets(line, MSG_SIZ, f);
10620             if (strncmp(line, "black", strlen("black"))==0)
10621               blackPlaysFirst = TRUE;
10622         }
10623     }
10624     startedFromSetupPosition = TRUE;
10625
10626     SendToProgram("force\n", &first);
10627     CopyBoard(boards[0], initial_position);
10628     if (blackPlaysFirst) {
10629         currentMove = forwardMostMove = backwardMostMove = 1;
10630         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10631         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10632         CopyBoard(boards[1], initial_position);
10633         DisplayMessage("", _("Black to play"));
10634     } else {
10635         currentMove = forwardMostMove = backwardMostMove = 0;
10636         DisplayMessage("", _("White to play"));
10637     }
10638     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10639     SendBoard(&first, forwardMostMove);
10640     if (appData.debugMode) {
10641 int i, j;
10642   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10643   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10644         fprintf(debugFP, "Load Position\n");
10645     }
10646
10647     if (positionNumber > 1) {
10648       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10649         DisplayTitle(line);
10650     } else {
10651         DisplayTitle(title);
10652     }
10653     gameMode = EditGame;
10654     ModeHighlight();
10655     ResetClocks();
10656     timeRemaining[0][1] = whiteTimeRemaining;
10657     timeRemaining[1][1] = blackTimeRemaining;
10658     DrawPosition(FALSE, boards[currentMove]);
10659
10660     return TRUE;
10661 }
10662
10663
10664 void
10665 CopyPlayerNameIntoFileName(dest, src)
10666      char **dest, *src;
10667 {
10668     while (*src != NULLCHAR && *src != ',') {
10669         if (*src == ' ') {
10670             *(*dest)++ = '_';
10671             src++;
10672         } else {
10673             *(*dest)++ = *src++;
10674         }
10675     }
10676 }
10677
10678 char *DefaultFileName(ext)
10679      char *ext;
10680 {
10681     static char def[MSG_SIZ];
10682     char *p;
10683
10684     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10685         p = def;
10686         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10687         *p++ = '-';
10688         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10689         *p++ = '.';
10690         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10691     } else {
10692         def[0] = NULLCHAR;
10693     }
10694     return def;
10695 }
10696
10697 /* Save the current game to the given file */
10698 int
10699 SaveGameToFile(filename, append)
10700      char *filename;
10701      int append;
10702 {
10703     FILE *f;
10704     char buf[MSG_SIZ];
10705
10706     if (strcmp(filename, "-") == 0) {
10707         return SaveGame(stdout, 0, NULL);
10708     } else {
10709         f = fopen(filename, append ? "a" : "w");
10710         if (f == NULL) {
10711             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10712             DisplayError(buf, errno);
10713             return FALSE;
10714         } else {
10715             return SaveGame(f, 0, NULL);
10716         }
10717     }
10718 }
10719
10720 char *
10721 SavePart(str)
10722      char *str;
10723 {
10724     static char buf[MSG_SIZ];
10725     char *p;
10726
10727     p = strchr(str, ' ');
10728     if (p == NULL) return str;
10729     strncpy(buf, str, p - str);
10730     buf[p - str] = NULLCHAR;
10731     return buf;
10732 }
10733
10734 #define PGN_MAX_LINE 75
10735
10736 #define PGN_SIDE_WHITE  0
10737 #define PGN_SIDE_BLACK  1
10738
10739 /* [AS] */
10740 static int FindFirstMoveOutOfBook( int side )
10741 {
10742     int result = -1;
10743
10744     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10745         int index = backwardMostMove;
10746         int has_book_hit = 0;
10747
10748         if( (index % 2) != side ) {
10749             index++;
10750         }
10751
10752         while( index < forwardMostMove ) {
10753             /* Check to see if engine is in book */
10754             int depth = pvInfoList[index].depth;
10755             int score = pvInfoList[index].score;
10756             int in_book = 0;
10757
10758             if( depth <= 2 ) {
10759                 in_book = 1;
10760             }
10761             else if( score == 0 && depth == 63 ) {
10762                 in_book = 1; /* Zappa */
10763             }
10764             else if( score == 2 && depth == 99 ) {
10765                 in_book = 1; /* Abrok */
10766             }
10767
10768             has_book_hit += in_book;
10769
10770             if( ! in_book ) {
10771                 result = index;
10772
10773                 break;
10774             }
10775
10776             index += 2;
10777         }
10778     }
10779
10780     return result;
10781 }
10782
10783 /* [AS] */
10784 void GetOutOfBookInfo( char * buf )
10785 {
10786     int oob[2];
10787     int i;
10788     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10789
10790     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10791     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10792
10793     *buf = '\0';
10794
10795     if( oob[0] >= 0 || oob[1] >= 0 ) {
10796         for( i=0; i<2; i++ ) {
10797             int idx = oob[i];
10798
10799             if( idx >= 0 ) {
10800                 if( i > 0 && oob[0] >= 0 ) {
10801                     strcat( buf, "   " );
10802                 }
10803
10804                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10805                 sprintf( buf+strlen(buf), "%s%.2f",
10806                     pvInfoList[idx].score >= 0 ? "+" : "",
10807                     pvInfoList[idx].score / 100.0 );
10808             }
10809         }
10810     }
10811 }
10812
10813 /* Save game in PGN style and close the file */
10814 int
10815 SaveGamePGN(f)
10816      FILE *f;
10817 {
10818     int i, offset, linelen, newblock;
10819     time_t tm;
10820 //    char *movetext;
10821     char numtext[32];
10822     int movelen, numlen, blank;
10823     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10824
10825     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10826
10827     tm = time((time_t *) NULL);
10828
10829     PrintPGNTags(f, &gameInfo);
10830
10831     if (backwardMostMove > 0 || startedFromSetupPosition) {
10832         char *fen = PositionToFEN(backwardMostMove, NULL);
10833         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10834         fprintf(f, "\n{--------------\n");
10835         PrintPosition(f, backwardMostMove);
10836         fprintf(f, "--------------}\n");
10837         free(fen);
10838     }
10839     else {
10840         /* [AS] Out of book annotation */
10841         if( appData.saveOutOfBookInfo ) {
10842             char buf[64];
10843
10844             GetOutOfBookInfo( buf );
10845
10846             if( buf[0] != '\0' ) {
10847                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10848             }
10849         }
10850
10851         fprintf(f, "\n");
10852     }
10853
10854     i = backwardMostMove;
10855     linelen = 0;
10856     newblock = TRUE;
10857
10858     while (i < forwardMostMove) {
10859         /* Print comments preceding this move */
10860         if (commentList[i] != NULL) {
10861             if (linelen > 0) fprintf(f, "\n");
10862             fprintf(f, "%s", commentList[i]);
10863             linelen = 0;
10864             newblock = TRUE;
10865         }
10866
10867         /* Format move number */
10868         if ((i % 2) == 0)
10869           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10870         else
10871           if (newblock)
10872             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10873           else
10874             numtext[0] = NULLCHAR;
10875
10876         numlen = strlen(numtext);
10877         newblock = FALSE;
10878
10879         /* Print move number */
10880         blank = linelen > 0 && numlen > 0;
10881         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10882             fprintf(f, "\n");
10883             linelen = 0;
10884             blank = 0;
10885         }
10886         if (blank) {
10887             fprintf(f, " ");
10888             linelen++;
10889         }
10890         fprintf(f, "%s", numtext);
10891         linelen += numlen;
10892
10893         /* Get move */
10894         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10895         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10896
10897         /* Print move */
10898         blank = linelen > 0 && movelen > 0;
10899         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10900             fprintf(f, "\n");
10901             linelen = 0;
10902             blank = 0;
10903         }
10904         if (blank) {
10905             fprintf(f, " ");
10906             linelen++;
10907         }
10908         fprintf(f, "%s", move_buffer);
10909         linelen += movelen;
10910
10911         /* [AS] Add PV info if present */
10912         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10913             /* [HGM] add time */
10914             char buf[MSG_SIZ]; int seconds;
10915
10916             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10917
10918             if( seconds <= 0)
10919               buf[0] = 0;
10920             else
10921               if( seconds < 30 )
10922                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10923               else
10924                 {
10925                   seconds = (seconds + 4)/10; // round to full seconds
10926                   if( seconds < 60 )
10927                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10928                   else
10929                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10930                 }
10931
10932             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10933                       pvInfoList[i].score >= 0 ? "+" : "",
10934                       pvInfoList[i].score / 100.0,
10935                       pvInfoList[i].depth,
10936                       buf );
10937
10938             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10939
10940             /* Print score/depth */
10941             blank = linelen > 0 && movelen > 0;
10942             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10943                 fprintf(f, "\n");
10944                 linelen = 0;
10945                 blank = 0;
10946             }
10947             if (blank) {
10948                 fprintf(f, " ");
10949                 linelen++;
10950             }
10951             fprintf(f, "%s", move_buffer);
10952             linelen += movelen;
10953         }
10954
10955         i++;
10956     }
10957
10958     /* Start a new line */
10959     if (linelen > 0) fprintf(f, "\n");
10960
10961     /* Print comments after last move */
10962     if (commentList[i] != NULL) {
10963         fprintf(f, "%s\n", commentList[i]);
10964     }
10965
10966     /* Print result */
10967     if (gameInfo.resultDetails != NULL &&
10968         gameInfo.resultDetails[0] != NULLCHAR) {
10969         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10970                 PGNResult(gameInfo.result));
10971     } else {
10972         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10973     }
10974
10975     fclose(f);
10976     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10977     return TRUE;
10978 }
10979
10980 /* Save game in old style and close the file */
10981 int
10982 SaveGameOldStyle(f)
10983      FILE *f;
10984 {
10985     int i, offset;
10986     time_t tm;
10987
10988     tm = time((time_t *) NULL);
10989
10990     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10991     PrintOpponents(f);
10992
10993     if (backwardMostMove > 0 || startedFromSetupPosition) {
10994         fprintf(f, "\n[--------------\n");
10995         PrintPosition(f, backwardMostMove);
10996         fprintf(f, "--------------]\n");
10997     } else {
10998         fprintf(f, "\n");
10999     }
11000
11001     i = backwardMostMove;
11002     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11003
11004     while (i < forwardMostMove) {
11005         if (commentList[i] != NULL) {
11006             fprintf(f, "[%s]\n", commentList[i]);
11007         }
11008
11009         if ((i % 2) == 1) {
11010             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11011             i++;
11012         } else {
11013             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11014             i++;
11015             if (commentList[i] != NULL) {
11016                 fprintf(f, "\n");
11017                 continue;
11018             }
11019             if (i >= forwardMostMove) {
11020                 fprintf(f, "\n");
11021                 break;
11022             }
11023             fprintf(f, "%s\n", parseList[i]);
11024             i++;
11025         }
11026     }
11027
11028     if (commentList[i] != NULL) {
11029         fprintf(f, "[%s]\n", commentList[i]);
11030     }
11031
11032     /* This isn't really the old style, but it's close enough */
11033     if (gameInfo.resultDetails != NULL &&
11034         gameInfo.resultDetails[0] != NULLCHAR) {
11035         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11036                 gameInfo.resultDetails);
11037     } else {
11038         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11039     }
11040
11041     fclose(f);
11042     return TRUE;
11043 }
11044
11045 /* Save the current game to open file f and close the file */
11046 int
11047 SaveGame(f, dummy, dummy2)
11048      FILE *f;
11049      int dummy;
11050      char *dummy2;
11051 {
11052     if (gameMode == EditPosition) EditPositionDone(TRUE);
11053     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11054     if (appData.oldSaveStyle)
11055       return SaveGameOldStyle(f);
11056     else
11057       return SaveGamePGN(f);
11058 }
11059
11060 /* Save the current position to the given file */
11061 int
11062 SavePositionToFile(filename)
11063      char *filename;
11064 {
11065     FILE *f;
11066     char buf[MSG_SIZ];
11067
11068     if (strcmp(filename, "-") == 0) {
11069         return SavePosition(stdout, 0, NULL);
11070     } else {
11071         f = fopen(filename, "a");
11072         if (f == NULL) {
11073             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11074             DisplayError(buf, errno);
11075             return FALSE;
11076         } else {
11077             SavePosition(f, 0, NULL);
11078             return TRUE;
11079         }
11080     }
11081 }
11082
11083 /* Save the current position to the given open file and close the file */
11084 int
11085 SavePosition(f, dummy, dummy2)
11086      FILE *f;
11087      int dummy;
11088      char *dummy2;
11089 {
11090     time_t tm;
11091     char *fen;
11092
11093     if (gameMode == EditPosition) EditPositionDone(TRUE);
11094     if (appData.oldSaveStyle) {
11095         tm = time((time_t *) NULL);
11096
11097         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11098         PrintOpponents(f);
11099         fprintf(f, "[--------------\n");
11100         PrintPosition(f, currentMove);
11101         fprintf(f, "--------------]\n");
11102     } else {
11103         fen = PositionToFEN(currentMove, NULL);
11104         fprintf(f, "%s\n", fen);
11105         free(fen);
11106     }
11107     fclose(f);
11108     return TRUE;
11109 }
11110
11111 void
11112 ReloadCmailMsgEvent(unregister)
11113      int unregister;
11114 {
11115 #if !WIN32
11116     static char *inFilename = NULL;
11117     static char *outFilename;
11118     int i;
11119     struct stat inbuf, outbuf;
11120     int status;
11121
11122     /* Any registered moves are unregistered if unregister is set, */
11123     /* i.e. invoked by the signal handler */
11124     if (unregister) {
11125         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11126             cmailMoveRegistered[i] = FALSE;
11127             if (cmailCommentList[i] != NULL) {
11128                 free(cmailCommentList[i]);
11129                 cmailCommentList[i] = NULL;
11130             }
11131         }
11132         nCmailMovesRegistered = 0;
11133     }
11134
11135     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11136         cmailResult[i] = CMAIL_NOT_RESULT;
11137     }
11138     nCmailResults = 0;
11139
11140     if (inFilename == NULL) {
11141         /* Because the filenames are static they only get malloced once  */
11142         /* and they never get freed                                      */
11143         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11144         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11145
11146         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11147         sprintf(outFilename, "%s.out", appData.cmailGameName);
11148     }
11149
11150     status = stat(outFilename, &outbuf);
11151     if (status < 0) {
11152         cmailMailedMove = FALSE;
11153     } else {
11154         status = stat(inFilename, &inbuf);
11155         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11156     }
11157
11158     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11159        counts the games, notes how each one terminated, etc.
11160
11161        It would be nice to remove this kludge and instead gather all
11162        the information while building the game list.  (And to keep it
11163        in the game list nodes instead of having a bunch of fixed-size
11164        parallel arrays.)  Note this will require getting each game's
11165        termination from the PGN tags, as the game list builder does
11166        not process the game moves.  --mann
11167        */
11168     cmailMsgLoaded = TRUE;
11169     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11170
11171     /* Load first game in the file or popup game menu */
11172     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11173
11174 #endif /* !WIN32 */
11175     return;
11176 }
11177
11178 int
11179 RegisterMove()
11180 {
11181     FILE *f;
11182     char string[MSG_SIZ];
11183
11184     if (   cmailMailedMove
11185         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11186         return TRUE;            /* Allow free viewing  */
11187     }
11188
11189     /* Unregister move to ensure that we don't leave RegisterMove        */
11190     /* with the move registered when the conditions for registering no   */
11191     /* longer hold                                                       */
11192     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11193         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11194         nCmailMovesRegistered --;
11195
11196         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11197           {
11198               free(cmailCommentList[lastLoadGameNumber - 1]);
11199               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11200           }
11201     }
11202
11203     if (cmailOldMove == -1) {
11204         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11205         return FALSE;
11206     }
11207
11208     if (currentMove > cmailOldMove + 1) {
11209         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11210         return FALSE;
11211     }
11212
11213     if (currentMove < cmailOldMove) {
11214         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11215         return FALSE;
11216     }
11217
11218     if (forwardMostMove > currentMove) {
11219         /* Silently truncate extra moves */
11220         TruncateGame();
11221     }
11222
11223     if (   (currentMove == cmailOldMove + 1)
11224         || (   (currentMove == cmailOldMove)
11225             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11226                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11227         if (gameInfo.result != GameUnfinished) {
11228             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11229         }
11230
11231         if (commentList[currentMove] != NULL) {
11232             cmailCommentList[lastLoadGameNumber - 1]
11233               = StrSave(commentList[currentMove]);
11234         }
11235         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11236
11237         if (appData.debugMode)
11238           fprintf(debugFP, "Saving %s for game %d\n",
11239                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11240
11241         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11242
11243         f = fopen(string, "w");
11244         if (appData.oldSaveStyle) {
11245             SaveGameOldStyle(f); /* also closes the file */
11246
11247             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11248             f = fopen(string, "w");
11249             SavePosition(f, 0, NULL); /* also closes the file */
11250         } else {
11251             fprintf(f, "{--------------\n");
11252             PrintPosition(f, currentMove);
11253             fprintf(f, "--------------}\n\n");
11254
11255             SaveGame(f, 0, NULL); /* also closes the file*/
11256         }
11257
11258         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11259         nCmailMovesRegistered ++;
11260     } else if (nCmailGames == 1) {
11261         DisplayError(_("You have not made a move yet"), 0);
11262         return FALSE;
11263     }
11264
11265     return TRUE;
11266 }
11267
11268 void
11269 MailMoveEvent()
11270 {
11271 #if !WIN32
11272     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11273     FILE *commandOutput;
11274     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11275     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11276     int nBuffers;
11277     int i;
11278     int archived;
11279     char *arcDir;
11280
11281     if (! cmailMsgLoaded) {
11282         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11283         return;
11284     }
11285
11286     if (nCmailGames == nCmailResults) {
11287         DisplayError(_("No unfinished games"), 0);
11288         return;
11289     }
11290
11291 #if CMAIL_PROHIBIT_REMAIL
11292     if (cmailMailedMove) {
11293       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);
11294         DisplayError(msg, 0);
11295         return;
11296     }
11297 #endif
11298
11299     if (! (cmailMailedMove || RegisterMove())) return;
11300
11301     if (   cmailMailedMove
11302         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11303       snprintf(string, MSG_SIZ, partCommandString,
11304                appData.debugMode ? " -v" : "", appData.cmailGameName);
11305         commandOutput = popen(string, "r");
11306
11307         if (commandOutput == NULL) {
11308             DisplayError(_("Failed to invoke cmail"), 0);
11309         } else {
11310             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11311                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11312             }
11313             if (nBuffers > 1) {
11314                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11315                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11316                 nBytes = MSG_SIZ - 1;
11317             } else {
11318                 (void) memcpy(msg, buffer, nBytes);
11319             }
11320             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11321
11322             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11323                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11324
11325                 archived = TRUE;
11326                 for (i = 0; i < nCmailGames; i ++) {
11327                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11328                         archived = FALSE;
11329                     }
11330                 }
11331                 if (   archived
11332                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11333                         != NULL)) {
11334                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11335                            arcDir,
11336                            appData.cmailGameName,
11337                            gameInfo.date);
11338                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11339                     cmailMsgLoaded = FALSE;
11340                 }
11341             }
11342
11343             DisplayInformation(msg);
11344             pclose(commandOutput);
11345         }
11346     } else {
11347         if ((*cmailMsg) != '\0') {
11348             DisplayInformation(cmailMsg);
11349         }
11350     }
11351
11352     return;
11353 #endif /* !WIN32 */
11354 }
11355
11356 char *
11357 CmailMsg()
11358 {
11359 #if WIN32
11360     return NULL;
11361 #else
11362     int  prependComma = 0;
11363     char number[5];
11364     char string[MSG_SIZ];       /* Space for game-list */
11365     int  i;
11366
11367     if (!cmailMsgLoaded) return "";
11368
11369     if (cmailMailedMove) {
11370       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11371     } else {
11372         /* Create a list of games left */
11373       snprintf(string, MSG_SIZ, "[");
11374         for (i = 0; i < nCmailGames; i ++) {
11375             if (! (   cmailMoveRegistered[i]
11376                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11377                 if (prependComma) {
11378                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11379                 } else {
11380                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11381                     prependComma = 1;
11382                 }
11383
11384                 strcat(string, number);
11385             }
11386         }
11387         strcat(string, "]");
11388
11389         if (nCmailMovesRegistered + nCmailResults == 0) {
11390             switch (nCmailGames) {
11391               case 1:
11392                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11393                 break;
11394
11395               case 2:
11396                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11397                 break;
11398
11399               default:
11400                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11401                          nCmailGames);
11402                 break;
11403             }
11404         } else {
11405             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11406               case 1:
11407                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11408                          string);
11409                 break;
11410
11411               case 0:
11412                 if (nCmailResults == nCmailGames) {
11413                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11414                 } else {
11415                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11416                 }
11417                 break;
11418
11419               default:
11420                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11421                          string);
11422             }
11423         }
11424     }
11425     return cmailMsg;
11426 #endif /* WIN32 */
11427 }
11428
11429 void
11430 ResetGameEvent()
11431 {
11432     if (gameMode == Training)
11433       SetTrainingModeOff();
11434
11435     Reset(TRUE, TRUE);
11436     cmailMsgLoaded = FALSE;
11437     if (appData.icsActive) {
11438       SendToICS(ics_prefix);
11439       SendToICS("refresh\n");
11440     }
11441 }
11442
11443 void
11444 ExitEvent(status)
11445      int status;
11446 {
11447     exiting++;
11448     if (exiting > 2) {
11449       /* Give up on clean exit */
11450       exit(status);
11451     }
11452     if (exiting > 1) {
11453       /* Keep trying for clean exit */
11454       return;
11455     }
11456
11457     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11458
11459     if (telnetISR != NULL) {
11460       RemoveInputSource(telnetISR);
11461     }
11462     if (icsPR != NoProc) {
11463       DestroyChildProcess(icsPR, TRUE);
11464     }
11465
11466     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11467     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11468
11469     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11470     /* make sure this other one finishes before killing it!                  */
11471     if(endingGame) { int count = 0;
11472         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11473         while(endingGame && count++ < 10) DoSleep(1);
11474         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11475     }
11476
11477     /* Kill off chess programs */
11478     if (first.pr != NoProc) {
11479         ExitAnalyzeMode();
11480
11481         DoSleep( appData.delayBeforeQuit );
11482         SendToProgram("quit\n", &first);
11483         DoSleep( appData.delayAfterQuit );
11484         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11485     }
11486     if (second.pr != NoProc) {
11487         DoSleep( appData.delayBeforeQuit );
11488         SendToProgram("quit\n", &second);
11489         DoSleep( appData.delayAfterQuit );
11490         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11491     }
11492     if (first.isr != NULL) {
11493         RemoveInputSource(first.isr);
11494     }
11495     if (second.isr != NULL) {
11496         RemoveInputSource(second.isr);
11497     }
11498
11499     ShutDownFrontEnd();
11500     exit(status);
11501 }
11502
11503 void
11504 PauseEvent()
11505 {
11506     if (appData.debugMode)
11507         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11508     if (pausing) {
11509         pausing = FALSE;
11510         ModeHighlight();
11511         if (gameMode == MachinePlaysWhite ||
11512             gameMode == MachinePlaysBlack) {
11513             StartClocks();
11514         } else {
11515             DisplayBothClocks();
11516         }
11517         if (gameMode == PlayFromGameFile) {
11518             if (appData.timeDelay >= 0)
11519                 AutoPlayGameLoop();
11520         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11521             Reset(FALSE, TRUE);
11522             SendToICS(ics_prefix);
11523             SendToICS("refresh\n");
11524         } else if (currentMove < forwardMostMove) {
11525             ForwardInner(forwardMostMove);
11526         }
11527         pauseExamInvalid = FALSE;
11528     } else {
11529         switch (gameMode) {
11530           default:
11531             return;
11532           case IcsExamining:
11533             pauseExamForwardMostMove = forwardMostMove;
11534             pauseExamInvalid = FALSE;
11535             /* fall through */
11536           case IcsObserving:
11537           case IcsPlayingWhite:
11538           case IcsPlayingBlack:
11539             pausing = TRUE;
11540             ModeHighlight();
11541             return;
11542           case PlayFromGameFile:
11543             (void) StopLoadGameTimer();
11544             pausing = TRUE;
11545             ModeHighlight();
11546             break;
11547           case BeginningOfGame:
11548             if (appData.icsActive) return;
11549             /* else fall through */
11550           case MachinePlaysWhite:
11551           case MachinePlaysBlack:
11552           case TwoMachinesPlay:
11553             if (forwardMostMove == 0)
11554               return;           /* don't pause if no one has moved */
11555             if ((gameMode == MachinePlaysWhite &&
11556                  !WhiteOnMove(forwardMostMove)) ||
11557                 (gameMode == MachinePlaysBlack &&
11558                  WhiteOnMove(forwardMostMove))) {
11559                 StopClocks();
11560             }
11561             pausing = TRUE;
11562             ModeHighlight();
11563             break;
11564         }
11565     }
11566 }
11567
11568 void
11569 EditCommentEvent()
11570 {
11571     char title[MSG_SIZ];
11572
11573     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11574       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11575     } else {
11576       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11577                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11578                parseList[currentMove - 1]);
11579     }
11580
11581     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11582 }
11583
11584
11585 void
11586 EditTagsEvent()
11587 {
11588     char *tags = PGNTags(&gameInfo);
11589     EditTagsPopUp(tags);
11590     free(tags);
11591 }
11592
11593 void
11594 AnalyzeModeEvent()
11595 {
11596     if (appData.noChessProgram || gameMode == AnalyzeMode)
11597       return;
11598
11599     if (gameMode != AnalyzeFile) {
11600         if (!appData.icsEngineAnalyze) {
11601                EditGameEvent();
11602                if (gameMode != EditGame) return;
11603         }
11604         ResurrectChessProgram();
11605         SendToProgram("analyze\n", &first);
11606         first.analyzing = TRUE;
11607         /*first.maybeThinking = TRUE;*/
11608         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11609         EngineOutputPopUp();
11610     }
11611     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11612     pausing = FALSE;
11613     ModeHighlight();
11614     SetGameInfo();
11615
11616     StartAnalysisClock();
11617     GetTimeMark(&lastNodeCountTime);
11618     lastNodeCount = 0;
11619 }
11620
11621 void
11622 AnalyzeFileEvent()
11623 {
11624     if (appData.noChessProgram || gameMode == AnalyzeFile)
11625       return;
11626
11627     if (gameMode != AnalyzeMode) {
11628         EditGameEvent();
11629         if (gameMode != EditGame) return;
11630         ResurrectChessProgram();
11631         SendToProgram("analyze\n", &first);
11632         first.analyzing = TRUE;
11633         /*first.maybeThinking = TRUE;*/
11634         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11635         EngineOutputPopUp();
11636     }
11637     gameMode = AnalyzeFile;
11638     pausing = FALSE;
11639     ModeHighlight();
11640     SetGameInfo();
11641
11642     StartAnalysisClock();
11643     GetTimeMark(&lastNodeCountTime);
11644     lastNodeCount = 0;
11645 }
11646
11647 void
11648 MachineWhiteEvent()
11649 {
11650     char buf[MSG_SIZ];
11651     char *bookHit = NULL;
11652
11653     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11654       return;
11655
11656
11657     if (gameMode == PlayFromGameFile ||
11658         gameMode == TwoMachinesPlay  ||
11659         gameMode == Training         ||
11660         gameMode == AnalyzeMode      ||
11661         gameMode == EndOfGame)
11662         EditGameEvent();
11663
11664     if (gameMode == EditPosition)
11665         EditPositionDone(TRUE);
11666
11667     if (!WhiteOnMove(currentMove)) {
11668         DisplayError(_("It is not White's turn"), 0);
11669         return;
11670     }
11671
11672     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11673       ExitAnalyzeMode();
11674
11675     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11676         gameMode == AnalyzeFile)
11677         TruncateGame();
11678
11679     ResurrectChessProgram();    /* in case it isn't running */
11680     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11681         gameMode = MachinePlaysWhite;
11682         ResetClocks();
11683     } else
11684     gameMode = MachinePlaysWhite;
11685     pausing = FALSE;
11686     ModeHighlight();
11687     SetGameInfo();
11688     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11689     DisplayTitle(buf);
11690     if (first.sendName) {
11691       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11692       SendToProgram(buf, &first);
11693     }
11694     if (first.sendTime) {
11695       if (first.useColors) {
11696         SendToProgram("black\n", &first); /*gnu kludge*/
11697       }
11698       SendTimeRemaining(&first, TRUE);
11699     }
11700     if (first.useColors) {
11701       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11702     }
11703     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11704     SetMachineThinkingEnables();
11705     first.maybeThinking = TRUE;
11706     StartClocks();
11707     firstMove = FALSE;
11708
11709     if (appData.autoFlipView && !flipView) {
11710       flipView = !flipView;
11711       DrawPosition(FALSE, NULL);
11712       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11713     }
11714
11715     if(bookHit) { // [HGM] book: simulate book reply
11716         static char bookMove[MSG_SIZ]; // a bit generous?
11717
11718         programStats.nodes = programStats.depth = programStats.time =
11719         programStats.score = programStats.got_only_move = 0;
11720         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11721
11722         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11723         strcat(bookMove, bookHit);
11724         HandleMachineMove(bookMove, &first);
11725     }
11726 }
11727
11728 void
11729 MachineBlackEvent()
11730 {
11731   char buf[MSG_SIZ];
11732   char *bookHit = NULL;
11733
11734     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11735         return;
11736
11737
11738     if (gameMode == PlayFromGameFile ||
11739         gameMode == TwoMachinesPlay  ||
11740         gameMode == Training         ||
11741         gameMode == AnalyzeMode      ||
11742         gameMode == EndOfGame)
11743         EditGameEvent();
11744
11745     if (gameMode == EditPosition)
11746         EditPositionDone(TRUE);
11747
11748     if (WhiteOnMove(currentMove)) {
11749         DisplayError(_("It is not Black's turn"), 0);
11750         return;
11751     }
11752
11753     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11754       ExitAnalyzeMode();
11755
11756     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11757         gameMode == AnalyzeFile)
11758         TruncateGame();
11759
11760     ResurrectChessProgram();    /* in case it isn't running */
11761     gameMode = MachinePlaysBlack;
11762     pausing = FALSE;
11763     ModeHighlight();
11764     SetGameInfo();
11765     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11766     DisplayTitle(buf);
11767     if (first.sendName) {
11768       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11769       SendToProgram(buf, &first);
11770     }
11771     if (first.sendTime) {
11772       if (first.useColors) {
11773         SendToProgram("white\n", &first); /*gnu kludge*/
11774       }
11775       SendTimeRemaining(&first, FALSE);
11776     }
11777     if (first.useColors) {
11778       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11779     }
11780     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11781     SetMachineThinkingEnables();
11782     first.maybeThinking = TRUE;
11783     StartClocks();
11784
11785     if (appData.autoFlipView && flipView) {
11786       flipView = !flipView;
11787       DrawPosition(FALSE, NULL);
11788       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11789     }
11790     if(bookHit) { // [HGM] book: simulate book reply
11791         static char bookMove[MSG_SIZ]; // a bit generous?
11792
11793         programStats.nodes = programStats.depth = programStats.time =
11794         programStats.score = programStats.got_only_move = 0;
11795         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11796
11797         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11798         strcat(bookMove, bookHit);
11799         HandleMachineMove(bookMove, &first);
11800     }
11801 }
11802
11803
11804 void
11805 DisplayTwoMachinesTitle()
11806 {
11807     char buf[MSG_SIZ];
11808     if (appData.matchGames > 0) {
11809         if (first.twoMachinesColor[0] == 'w') {
11810           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11811                    gameInfo.white, gameInfo.black,
11812                    first.matchWins, second.matchWins,
11813                    matchGame - 1 - (first.matchWins + second.matchWins));
11814         } else {
11815           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11816                    gameInfo.white, gameInfo.black,
11817                    second.matchWins, first.matchWins,
11818                    matchGame - 1 - (first.matchWins + second.matchWins));
11819         }
11820     } else {
11821       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11822     }
11823     DisplayTitle(buf);
11824 }
11825
11826 void
11827 TwoMachinesEvent P((void))
11828 {
11829     int i;
11830     char buf[MSG_SIZ];
11831     ChessProgramState *onmove;
11832     char *bookHit = NULL;
11833
11834     if (appData.noChessProgram) return;
11835
11836     switch (gameMode) {
11837       case TwoMachinesPlay:
11838         return;
11839       case MachinePlaysWhite:
11840       case MachinePlaysBlack:
11841         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11842             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11843             return;
11844         }
11845         /* fall through */
11846       case BeginningOfGame:
11847       case PlayFromGameFile:
11848       case EndOfGame:
11849         EditGameEvent();
11850         if (gameMode != EditGame) return;
11851         break;
11852       case EditPosition:
11853         EditPositionDone(TRUE);
11854         break;
11855       case AnalyzeMode:
11856       case AnalyzeFile:
11857         ExitAnalyzeMode();
11858         break;
11859       case EditGame:
11860       default:
11861         break;
11862     }
11863
11864 //    forwardMostMove = currentMove;
11865     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11866     ResurrectChessProgram();    /* in case first program isn't running */
11867
11868     if (second.pr == NULL) {
11869         StartChessProgram(&second);
11870         if (second.protocolVersion == 1) {
11871           TwoMachinesEventIfReady();
11872         } else {
11873           /* kludge: allow timeout for initial "feature" command */
11874           FreezeUI();
11875           DisplayMessage("", _("Starting second chess program"));
11876           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11877         }
11878         return;
11879     }
11880     DisplayMessage("", "");
11881     InitChessProgram(&second, FALSE);
11882     SendToProgram("force\n", &second);
11883     if (startedFromSetupPosition) {
11884         SendBoard(&second, backwardMostMove);
11885     if (appData.debugMode) {
11886         fprintf(debugFP, "Two Machines\n");
11887     }
11888     }
11889     for (i = backwardMostMove; i < forwardMostMove; i++) {
11890         SendMoveToProgram(i, &second);
11891     }
11892
11893     gameMode = TwoMachinesPlay;
11894     pausing = FALSE;
11895     ModeHighlight();
11896     SetGameInfo();
11897     DisplayTwoMachinesTitle();
11898     firstMove = TRUE;
11899     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11900         onmove = &first;
11901     } else {
11902         onmove = &second;
11903     }
11904
11905     SendToProgram(first.computerString, &first);
11906     if (first.sendName) {
11907       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11908       SendToProgram(buf, &first);
11909     }
11910     SendToProgram(second.computerString, &second);
11911     if (second.sendName) {
11912       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11913       SendToProgram(buf, &second);
11914     }
11915
11916     ResetClocks();
11917     if (!first.sendTime || !second.sendTime) {
11918         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11919         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11920     }
11921     if (onmove->sendTime) {
11922       if (onmove->useColors) {
11923         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11924       }
11925       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11926     }
11927     if (onmove->useColors) {
11928       SendToProgram(onmove->twoMachinesColor, onmove);
11929     }
11930     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11931 //    SendToProgram("go\n", onmove);
11932     onmove->maybeThinking = TRUE;
11933     SetMachineThinkingEnables();
11934
11935     StartClocks();
11936
11937     if(bookHit) { // [HGM] book: simulate book reply
11938         static char bookMove[MSG_SIZ]; // a bit generous?
11939
11940         programStats.nodes = programStats.depth = programStats.time =
11941         programStats.score = programStats.got_only_move = 0;
11942         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11943
11944         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11945         strcat(bookMove, bookHit);
11946         savedMessage = bookMove; // args for deferred call
11947         savedState = onmove;
11948         ScheduleDelayedEvent(DeferredBookMove, 1);
11949     }
11950 }
11951
11952 void
11953 TrainingEvent()
11954 {
11955     if (gameMode == Training) {
11956       SetTrainingModeOff();
11957       gameMode = PlayFromGameFile;
11958       DisplayMessage("", _("Training mode off"));
11959     } else {
11960       gameMode = Training;
11961       animateTraining = appData.animate;
11962
11963       /* make sure we are not already at the end of the game */
11964       if (currentMove < forwardMostMove) {
11965         SetTrainingModeOn();
11966         DisplayMessage("", _("Training mode on"));
11967       } else {
11968         gameMode = PlayFromGameFile;
11969         DisplayError(_("Already at end of game"), 0);
11970       }
11971     }
11972     ModeHighlight();
11973 }
11974
11975 void
11976 IcsClientEvent()
11977 {
11978     if (!appData.icsActive) return;
11979     switch (gameMode) {
11980       case IcsPlayingWhite:
11981       case IcsPlayingBlack:
11982       case IcsObserving:
11983       case IcsIdle:
11984       case BeginningOfGame:
11985       case IcsExamining:
11986         return;
11987
11988       case EditGame:
11989         break;
11990
11991       case EditPosition:
11992         EditPositionDone(TRUE);
11993         break;
11994
11995       case AnalyzeMode:
11996       case AnalyzeFile:
11997         ExitAnalyzeMode();
11998         break;
11999
12000       default:
12001         EditGameEvent();
12002         break;
12003     }
12004
12005     gameMode = IcsIdle;
12006     ModeHighlight();
12007     return;
12008 }
12009
12010
12011 void
12012 EditGameEvent()
12013 {
12014     int i;
12015
12016     switch (gameMode) {
12017       case Training:
12018         SetTrainingModeOff();
12019         break;
12020       case MachinePlaysWhite:
12021       case MachinePlaysBlack:
12022       case BeginningOfGame:
12023         SendToProgram("force\n", &first);
12024         SetUserThinkingEnables();
12025         break;
12026       case PlayFromGameFile:
12027         (void) StopLoadGameTimer();
12028         if (gameFileFP != NULL) {
12029             gameFileFP = NULL;
12030         }
12031         break;
12032       case EditPosition:
12033         EditPositionDone(TRUE);
12034         break;
12035       case AnalyzeMode:
12036       case AnalyzeFile:
12037         ExitAnalyzeMode();
12038         SendToProgram("force\n", &first);
12039         break;
12040       case TwoMachinesPlay:
12041         GameEnds(EndOfFile, NULL, GE_PLAYER);
12042         ResurrectChessProgram();
12043         SetUserThinkingEnables();
12044         break;
12045       case EndOfGame:
12046         ResurrectChessProgram();
12047         break;
12048       case IcsPlayingBlack:
12049       case IcsPlayingWhite:
12050         DisplayError(_("Warning: You are still playing a game"), 0);
12051         break;
12052       case IcsObserving:
12053         DisplayError(_("Warning: You are still observing a game"), 0);
12054         break;
12055       case IcsExamining:
12056         DisplayError(_("Warning: You are still examining a game"), 0);
12057         break;
12058       case IcsIdle:
12059         break;
12060       case EditGame:
12061       default:
12062         return;
12063     }
12064
12065     pausing = FALSE;
12066     StopClocks();
12067     first.offeredDraw = second.offeredDraw = 0;
12068
12069     if (gameMode == PlayFromGameFile) {
12070         whiteTimeRemaining = timeRemaining[0][currentMove];
12071         blackTimeRemaining = timeRemaining[1][currentMove];
12072         DisplayTitle("");
12073     }
12074
12075     if (gameMode == MachinePlaysWhite ||
12076         gameMode == MachinePlaysBlack ||
12077         gameMode == TwoMachinesPlay ||
12078         gameMode == EndOfGame) {
12079         i = forwardMostMove;
12080         while (i > currentMove) {
12081             SendToProgram("undo\n", &first);
12082             i--;
12083         }
12084         whiteTimeRemaining = timeRemaining[0][currentMove];
12085         blackTimeRemaining = timeRemaining[1][currentMove];
12086         DisplayBothClocks();
12087         if (whiteFlag || blackFlag) {
12088             whiteFlag = blackFlag = 0;
12089         }
12090         DisplayTitle("");
12091     }
12092
12093     gameMode = EditGame;
12094     ModeHighlight();
12095     SetGameInfo();
12096 }
12097
12098
12099 void
12100 EditPositionEvent()
12101 {
12102     if (gameMode == EditPosition) {
12103         EditGameEvent();
12104         return;
12105     }
12106
12107     EditGameEvent();
12108     if (gameMode != EditGame) return;
12109
12110     gameMode = EditPosition;
12111     ModeHighlight();
12112     SetGameInfo();
12113     if (currentMove > 0)
12114       CopyBoard(boards[0], boards[currentMove]);
12115
12116     blackPlaysFirst = !WhiteOnMove(currentMove);
12117     ResetClocks();
12118     currentMove = forwardMostMove = backwardMostMove = 0;
12119     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12120     DisplayMove(-1);
12121 }
12122
12123 void
12124 ExitAnalyzeMode()
12125 {
12126     /* [DM] icsEngineAnalyze - possible call from other functions */
12127     if (appData.icsEngineAnalyze) {
12128         appData.icsEngineAnalyze = FALSE;
12129
12130         DisplayMessage("",_("Close ICS engine analyze..."));
12131     }
12132     if (first.analysisSupport && first.analyzing) {
12133       SendToProgram("exit\n", &first);
12134       first.analyzing = FALSE;
12135     }
12136     thinkOutput[0] = NULLCHAR;
12137 }
12138
12139 void
12140 EditPositionDone(Boolean fakeRights)
12141 {
12142     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12143
12144     startedFromSetupPosition = TRUE;
12145     InitChessProgram(&first, FALSE);
12146     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12147       boards[0][EP_STATUS] = EP_NONE;
12148       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12149     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12150         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12151         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12152       } else boards[0][CASTLING][2] = NoRights;
12153     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12154         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12155         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12156       } else boards[0][CASTLING][5] = NoRights;
12157     }
12158     SendToProgram("force\n", &first);
12159     if (blackPlaysFirst) {
12160         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12161         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12162         currentMove = forwardMostMove = backwardMostMove = 1;
12163         CopyBoard(boards[1], boards[0]);
12164     } else {
12165         currentMove = forwardMostMove = backwardMostMove = 0;
12166     }
12167     SendBoard(&first, forwardMostMove);
12168     if (appData.debugMode) {
12169         fprintf(debugFP, "EditPosDone\n");
12170     }
12171     DisplayTitle("");
12172     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12173     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12174     gameMode = EditGame;
12175     ModeHighlight();
12176     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12177     ClearHighlights(); /* [AS] */
12178 }
12179
12180 /* Pause for `ms' milliseconds */
12181 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12182 void
12183 TimeDelay(ms)
12184      long ms;
12185 {
12186     TimeMark m1, m2;
12187
12188     GetTimeMark(&m1);
12189     do {
12190         GetTimeMark(&m2);
12191     } while (SubtractTimeMarks(&m2, &m1) < ms);
12192 }
12193
12194 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12195 void
12196 SendMultiLineToICS(buf)
12197      char *buf;
12198 {
12199     char temp[MSG_SIZ+1], *p;
12200     int len;
12201
12202     len = strlen(buf);
12203     if (len > MSG_SIZ)
12204       len = MSG_SIZ;
12205
12206     strncpy(temp, buf, len);
12207     temp[len] = 0;
12208
12209     p = temp;
12210     while (*p) {
12211         if (*p == '\n' || *p == '\r')
12212           *p = ' ';
12213         ++p;
12214     }
12215
12216     strcat(temp, "\n");
12217     SendToICS(temp);
12218     SendToPlayer(temp, strlen(temp));
12219 }
12220
12221 void
12222 SetWhiteToPlayEvent()
12223 {
12224     if (gameMode == EditPosition) {
12225         blackPlaysFirst = FALSE;
12226         DisplayBothClocks();    /* works because currentMove is 0 */
12227     } else if (gameMode == IcsExamining) {
12228         SendToICS(ics_prefix);
12229         SendToICS("tomove white\n");
12230     }
12231 }
12232
12233 void
12234 SetBlackToPlayEvent()
12235 {
12236     if (gameMode == EditPosition) {
12237         blackPlaysFirst = TRUE;
12238         currentMove = 1;        /* kludge */
12239         DisplayBothClocks();
12240         currentMove = 0;
12241     } else if (gameMode == IcsExamining) {
12242         SendToICS(ics_prefix);
12243         SendToICS("tomove black\n");
12244     }
12245 }
12246
12247 void
12248 EditPositionMenuEvent(selection, x, y)
12249      ChessSquare selection;
12250      int x, y;
12251 {
12252     char buf[MSG_SIZ];
12253     ChessSquare piece = boards[0][y][x];
12254
12255     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12256
12257     switch (selection) {
12258       case ClearBoard:
12259         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12260             SendToICS(ics_prefix);
12261             SendToICS("bsetup clear\n");
12262         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12263             SendToICS(ics_prefix);
12264             SendToICS("clearboard\n");
12265         } else {
12266             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12267                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12268                 for (y = 0; y < BOARD_HEIGHT; y++) {
12269                     if (gameMode == IcsExamining) {
12270                         if (boards[currentMove][y][x] != EmptySquare) {
12271                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12272                                     AAA + x, ONE + y);
12273                             SendToICS(buf);
12274                         }
12275                     } else {
12276                         boards[0][y][x] = p;
12277                     }
12278                 }
12279             }
12280         }
12281         if (gameMode == EditPosition) {
12282             DrawPosition(FALSE, boards[0]);
12283         }
12284         break;
12285
12286       case WhitePlay:
12287         SetWhiteToPlayEvent();
12288         break;
12289
12290       case BlackPlay:
12291         SetBlackToPlayEvent();
12292         break;
12293
12294       case EmptySquare:
12295         if (gameMode == IcsExamining) {
12296             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12297             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12298             SendToICS(buf);
12299         } else {
12300             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12301                 if(x == BOARD_LEFT-2) {
12302                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12303                     boards[0][y][1] = 0;
12304                 } else
12305                 if(x == BOARD_RGHT+1) {
12306                     if(y >= gameInfo.holdingsSize) break;
12307                     boards[0][y][BOARD_WIDTH-2] = 0;
12308                 } else break;
12309             }
12310             boards[0][y][x] = EmptySquare;
12311             DrawPosition(FALSE, boards[0]);
12312         }
12313         break;
12314
12315       case PromotePiece:
12316         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12317            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12318             selection = (ChessSquare) (PROMOTED piece);
12319         } else if(piece == EmptySquare) selection = WhiteSilver;
12320         else selection = (ChessSquare)((int)piece - 1);
12321         goto defaultlabel;
12322
12323       case DemotePiece:
12324         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12325            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12326             selection = (ChessSquare) (DEMOTED piece);
12327         } else if(piece == EmptySquare) selection = BlackSilver;
12328         else selection = (ChessSquare)((int)piece + 1);
12329         goto defaultlabel;
12330
12331       case WhiteQueen:
12332       case BlackQueen:
12333         if(gameInfo.variant == VariantShatranj ||
12334            gameInfo.variant == VariantXiangqi  ||
12335            gameInfo.variant == VariantCourier  ||
12336            gameInfo.variant == VariantMakruk     )
12337             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12338         goto defaultlabel;
12339
12340       case WhiteKing:
12341       case BlackKing:
12342         if(gameInfo.variant == VariantXiangqi)
12343             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12344         if(gameInfo.variant == VariantKnightmate)
12345             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12346       default:
12347         defaultlabel:
12348         if (gameMode == IcsExamining) {
12349             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12350             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12351                      PieceToChar(selection), AAA + x, ONE + y);
12352             SendToICS(buf);
12353         } else {
12354             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12355                 int n;
12356                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12357                     n = PieceToNumber(selection - BlackPawn);
12358                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12359                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12360                     boards[0][BOARD_HEIGHT-1-n][1]++;
12361                 } else
12362                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12363                     n = PieceToNumber(selection);
12364                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12365                     boards[0][n][BOARD_WIDTH-1] = selection;
12366                     boards[0][n][BOARD_WIDTH-2]++;
12367                 }
12368             } else
12369             boards[0][y][x] = selection;
12370             DrawPosition(TRUE, boards[0]);
12371         }
12372         break;
12373     }
12374 }
12375
12376
12377 void
12378 DropMenuEvent(selection, x, y)
12379      ChessSquare selection;
12380      int x, y;
12381 {
12382     ChessMove moveType;
12383
12384     switch (gameMode) {
12385       case IcsPlayingWhite:
12386       case MachinePlaysBlack:
12387         if (!WhiteOnMove(currentMove)) {
12388             DisplayMoveError(_("It is Black's turn"));
12389             return;
12390         }
12391         moveType = WhiteDrop;
12392         break;
12393       case IcsPlayingBlack:
12394       case MachinePlaysWhite:
12395         if (WhiteOnMove(currentMove)) {
12396             DisplayMoveError(_("It is White's turn"));
12397             return;
12398         }
12399         moveType = BlackDrop;
12400         break;
12401       case EditGame:
12402         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12403         break;
12404       default:
12405         return;
12406     }
12407
12408     if (moveType == BlackDrop && selection < BlackPawn) {
12409       selection = (ChessSquare) ((int) selection
12410                                  + (int) BlackPawn - (int) WhitePawn);
12411     }
12412     if (boards[currentMove][y][x] != EmptySquare) {
12413         DisplayMoveError(_("That square is occupied"));
12414         return;
12415     }
12416
12417     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12418 }
12419
12420 void
12421 AcceptEvent()
12422 {
12423     /* Accept a pending offer of any kind from opponent */
12424
12425     if (appData.icsActive) {
12426         SendToICS(ics_prefix);
12427         SendToICS("accept\n");
12428     } else if (cmailMsgLoaded) {
12429         if (currentMove == cmailOldMove &&
12430             commentList[cmailOldMove] != NULL &&
12431             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12432                    "Black offers a draw" : "White offers a draw")) {
12433             TruncateGame();
12434             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12435             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12436         } else {
12437             DisplayError(_("There is no pending offer on this move"), 0);
12438             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12439         }
12440     } else {
12441         /* Not used for offers from chess program */
12442     }
12443 }
12444
12445 void
12446 DeclineEvent()
12447 {
12448     /* Decline a pending offer of any kind from opponent */
12449
12450     if (appData.icsActive) {
12451         SendToICS(ics_prefix);
12452         SendToICS("decline\n");
12453     } else if (cmailMsgLoaded) {
12454         if (currentMove == cmailOldMove &&
12455             commentList[cmailOldMove] != NULL &&
12456             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12457                    "Black offers a draw" : "White offers a draw")) {
12458 #ifdef NOTDEF
12459             AppendComment(cmailOldMove, "Draw declined", TRUE);
12460             DisplayComment(cmailOldMove - 1, "Draw declined");
12461 #endif /*NOTDEF*/
12462         } else {
12463             DisplayError(_("There is no pending offer on this move"), 0);
12464         }
12465     } else {
12466         /* Not used for offers from chess program */
12467     }
12468 }
12469
12470 void
12471 RematchEvent()
12472 {
12473     /* Issue ICS rematch command */
12474     if (appData.icsActive) {
12475         SendToICS(ics_prefix);
12476         SendToICS("rematch\n");
12477     }
12478 }
12479
12480 void
12481 CallFlagEvent()
12482 {
12483     /* Call your opponent's flag (claim a win on time) */
12484     if (appData.icsActive) {
12485         SendToICS(ics_prefix);
12486         SendToICS("flag\n");
12487     } else {
12488         switch (gameMode) {
12489           default:
12490             return;
12491           case MachinePlaysWhite:
12492             if (whiteFlag) {
12493                 if (blackFlag)
12494                   GameEnds(GameIsDrawn, "Both players ran out of time",
12495                            GE_PLAYER);
12496                 else
12497                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12498             } else {
12499                 DisplayError(_("Your opponent is not out of time"), 0);
12500             }
12501             break;
12502           case MachinePlaysBlack:
12503             if (blackFlag) {
12504                 if (whiteFlag)
12505                   GameEnds(GameIsDrawn, "Both players ran out of time",
12506                            GE_PLAYER);
12507                 else
12508                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12509             } else {
12510                 DisplayError(_("Your opponent is not out of time"), 0);
12511             }
12512             break;
12513         }
12514     }
12515 }
12516
12517 void
12518 DrawEvent()
12519 {
12520     /* Offer draw or accept pending draw offer from opponent */
12521
12522     if (appData.icsActive) {
12523         /* Note: tournament rules require draw offers to be
12524            made after you make your move but before you punch
12525            your clock.  Currently ICS doesn't let you do that;
12526            instead, you immediately punch your clock after making
12527            a move, but you can offer a draw at any time. */
12528
12529         SendToICS(ics_prefix);
12530         SendToICS("draw\n");
12531         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
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             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12538             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12539         } else if (currentMove == cmailOldMove + 1) {
12540             char *offer = WhiteOnMove(cmailOldMove) ?
12541               "White offers a draw" : "Black offers a draw";
12542             AppendComment(currentMove, offer, TRUE);
12543             DisplayComment(currentMove - 1, offer);
12544             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12545         } else {
12546             DisplayError(_("You must make your move before offering a draw"), 0);
12547             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12548         }
12549     } else if (first.offeredDraw) {
12550         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12551     } else {
12552         if (first.sendDrawOffers) {
12553             SendToProgram("draw\n", &first);
12554             userOfferedDraw = TRUE;
12555         }
12556     }
12557 }
12558
12559 void
12560 AdjournEvent()
12561 {
12562     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12563
12564     if (appData.icsActive) {
12565         SendToICS(ics_prefix);
12566         SendToICS("adjourn\n");
12567     } else {
12568         /* Currently GNU Chess doesn't offer or accept Adjourns */
12569     }
12570 }
12571
12572
12573 void
12574 AbortEvent()
12575 {
12576     /* Offer Abort or accept pending Abort offer from opponent */
12577
12578     if (appData.icsActive) {
12579         SendToICS(ics_prefix);
12580         SendToICS("abort\n");
12581     } else {
12582         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12583     }
12584 }
12585
12586 void
12587 ResignEvent()
12588 {
12589     /* Resign.  You can do this even if it's not your turn. */
12590
12591     if (appData.icsActive) {
12592         SendToICS(ics_prefix);
12593         SendToICS("resign\n");
12594     } else {
12595         switch (gameMode) {
12596           case MachinePlaysWhite:
12597             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12598             break;
12599           case MachinePlaysBlack:
12600             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12601             break;
12602           case EditGame:
12603             if (cmailMsgLoaded) {
12604                 TruncateGame();
12605                 if (WhiteOnMove(cmailOldMove)) {
12606                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12607                 } else {
12608                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12609                 }
12610                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12611             }
12612             break;
12613           default:
12614             break;
12615         }
12616     }
12617 }
12618
12619
12620 void
12621 StopObservingEvent()
12622 {
12623     /* Stop observing current games */
12624     SendToICS(ics_prefix);
12625     SendToICS("unobserve\n");
12626 }
12627
12628 void
12629 StopExaminingEvent()
12630 {
12631     /* Stop observing current game */
12632     SendToICS(ics_prefix);
12633     SendToICS("unexamine\n");
12634 }
12635
12636 void
12637 ForwardInner(target)
12638      int target;
12639 {
12640     int limit;
12641
12642     if (appData.debugMode)
12643         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12644                 target, currentMove, forwardMostMove);
12645
12646     if (gameMode == EditPosition)
12647       return;
12648
12649     if (gameMode == PlayFromGameFile && !pausing)
12650       PauseEvent();
12651
12652     if (gameMode == IcsExamining && pausing)
12653       limit = pauseExamForwardMostMove;
12654     else
12655       limit = forwardMostMove;
12656
12657     if (target > limit) target = limit;
12658
12659     if (target > 0 && moveList[target - 1][0]) {
12660         int fromX, fromY, toX, toY;
12661         toX = moveList[target - 1][2] - AAA;
12662         toY = moveList[target - 1][3] - ONE;
12663         if (moveList[target - 1][1] == '@') {
12664             if (appData.highlightLastMove) {
12665                 SetHighlights(-1, -1, toX, toY);
12666             }
12667         } else {
12668             fromX = moveList[target - 1][0] - AAA;
12669             fromY = moveList[target - 1][1] - ONE;
12670             if (target == currentMove + 1) {
12671                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12672             }
12673             if (appData.highlightLastMove) {
12674                 SetHighlights(fromX, fromY, toX, toY);
12675             }
12676         }
12677     }
12678     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12679         gameMode == Training || gameMode == PlayFromGameFile ||
12680         gameMode == AnalyzeFile) {
12681         while (currentMove < target) {
12682             SendMoveToProgram(currentMove++, &first);
12683         }
12684     } else {
12685         currentMove = target;
12686     }
12687
12688     if (gameMode == EditGame || gameMode == EndOfGame) {
12689         whiteTimeRemaining = timeRemaining[0][currentMove];
12690         blackTimeRemaining = timeRemaining[1][currentMove];
12691     }
12692     DisplayBothClocks();
12693     DisplayMove(currentMove - 1);
12694     DrawPosition(FALSE, boards[currentMove]);
12695     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12696     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12697         DisplayComment(currentMove - 1, commentList[currentMove]);
12698     }
12699 }
12700
12701
12702 void
12703 ForwardEvent()
12704 {
12705     if (gameMode == IcsExamining && !pausing) {
12706         SendToICS(ics_prefix);
12707         SendToICS("forward\n");
12708     } else {
12709         ForwardInner(currentMove + 1);
12710     }
12711 }
12712
12713 void
12714 ToEndEvent()
12715 {
12716     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12717         /* to optimze, we temporarily turn off analysis mode while we feed
12718          * the remaining moves to the engine. Otherwise we get analysis output
12719          * after each move.
12720          */
12721         if (first.analysisSupport) {
12722           SendToProgram("exit\nforce\n", &first);
12723           first.analyzing = FALSE;
12724         }
12725     }
12726
12727     if (gameMode == IcsExamining && !pausing) {
12728         SendToICS(ics_prefix);
12729         SendToICS("forward 999999\n");
12730     } else {
12731         ForwardInner(forwardMostMove);
12732     }
12733
12734     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12735         /* we have fed all the moves, so reactivate analysis mode */
12736         SendToProgram("analyze\n", &first);
12737         first.analyzing = TRUE;
12738         /*first.maybeThinking = TRUE;*/
12739         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12740     }
12741 }
12742
12743 void
12744 BackwardInner(target)
12745      int target;
12746 {
12747     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12748
12749     if (appData.debugMode)
12750         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12751                 target, currentMove, forwardMostMove);
12752
12753     if (gameMode == EditPosition) return;
12754     if (currentMove <= backwardMostMove) {
12755         ClearHighlights();
12756         DrawPosition(full_redraw, boards[currentMove]);
12757         return;
12758     }
12759     if (gameMode == PlayFromGameFile && !pausing)
12760       PauseEvent();
12761
12762     if (moveList[target][0]) {
12763         int fromX, fromY, toX, toY;
12764         toX = moveList[target][2] - AAA;
12765         toY = moveList[target][3] - ONE;
12766         if (moveList[target][1] == '@') {
12767             if (appData.highlightLastMove) {
12768                 SetHighlights(-1, -1, toX, toY);
12769             }
12770         } else {
12771             fromX = moveList[target][0] - AAA;
12772             fromY = moveList[target][1] - ONE;
12773             if (target == currentMove - 1) {
12774                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12775             }
12776             if (appData.highlightLastMove) {
12777                 SetHighlights(fromX, fromY, toX, toY);
12778             }
12779         }
12780     }
12781     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12782         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12783         while (currentMove > target) {
12784             SendToProgram("undo\n", &first);
12785             currentMove--;
12786         }
12787     } else {
12788         currentMove = target;
12789     }
12790
12791     if (gameMode == EditGame || gameMode == EndOfGame) {
12792         whiteTimeRemaining = timeRemaining[0][currentMove];
12793         blackTimeRemaining = timeRemaining[1][currentMove];
12794     }
12795     DisplayBothClocks();
12796     DisplayMove(currentMove - 1);
12797     DrawPosition(full_redraw, boards[currentMove]);
12798     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12799     // [HGM] PV info: routine tests if comment empty
12800     DisplayComment(currentMove - 1, commentList[currentMove]);
12801 }
12802
12803 void
12804 BackwardEvent()
12805 {
12806     if (gameMode == IcsExamining && !pausing) {
12807         SendToICS(ics_prefix);
12808         SendToICS("backward\n");
12809     } else {
12810         BackwardInner(currentMove - 1);
12811     }
12812 }
12813
12814 void
12815 ToStartEvent()
12816 {
12817     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12818         /* to optimize, we temporarily turn off analysis mode while we undo
12819          * all the moves. Otherwise we get analysis output after each undo.
12820          */
12821         if (first.analysisSupport) {
12822           SendToProgram("exit\nforce\n", &first);
12823           first.analyzing = FALSE;
12824         }
12825     }
12826
12827     if (gameMode == IcsExamining && !pausing) {
12828         SendToICS(ics_prefix);
12829         SendToICS("backward 999999\n");
12830     } else {
12831         BackwardInner(backwardMostMove);
12832     }
12833
12834     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12835         /* we have fed all the moves, so reactivate analysis mode */
12836         SendToProgram("analyze\n", &first);
12837         first.analyzing = TRUE;
12838         /*first.maybeThinking = TRUE;*/
12839         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12840     }
12841 }
12842
12843 void
12844 ToNrEvent(int to)
12845 {
12846   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12847   if (to >= forwardMostMove) to = forwardMostMove;
12848   if (to <= backwardMostMove) to = backwardMostMove;
12849   if (to < currentMove) {
12850     BackwardInner(to);
12851   } else {
12852     ForwardInner(to);
12853   }
12854 }
12855
12856 void
12857 RevertEvent(Boolean annotate)
12858 {
12859     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12860         return;
12861     }
12862     if (gameMode != IcsExamining) {
12863         DisplayError(_("You are not examining a game"), 0);
12864         return;
12865     }
12866     if (pausing) {
12867         DisplayError(_("You can't revert while pausing"), 0);
12868         return;
12869     }
12870     SendToICS(ics_prefix);
12871     SendToICS("revert\n");
12872 }
12873
12874 void
12875 RetractMoveEvent()
12876 {
12877     switch (gameMode) {
12878       case MachinePlaysWhite:
12879       case MachinePlaysBlack:
12880         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12881             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12882             return;
12883         }
12884         if (forwardMostMove < 2) return;
12885         currentMove = forwardMostMove = forwardMostMove - 2;
12886         whiteTimeRemaining = timeRemaining[0][currentMove];
12887         blackTimeRemaining = timeRemaining[1][currentMove];
12888         DisplayBothClocks();
12889         DisplayMove(currentMove - 1);
12890         ClearHighlights();/*!! could figure this out*/
12891         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12892         SendToProgram("remove\n", &first);
12893         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12894         break;
12895
12896       case BeginningOfGame:
12897       default:
12898         break;
12899
12900       case IcsPlayingWhite:
12901       case IcsPlayingBlack:
12902         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12903             SendToICS(ics_prefix);
12904             SendToICS("takeback 2\n");
12905         } else {
12906             SendToICS(ics_prefix);
12907             SendToICS("takeback 1\n");
12908         }
12909         break;
12910     }
12911 }
12912
12913 void
12914 MoveNowEvent()
12915 {
12916     ChessProgramState *cps;
12917
12918     switch (gameMode) {
12919       case MachinePlaysWhite:
12920         if (!WhiteOnMove(forwardMostMove)) {
12921             DisplayError(_("It is your turn"), 0);
12922             return;
12923         }
12924         cps = &first;
12925         break;
12926       case MachinePlaysBlack:
12927         if (WhiteOnMove(forwardMostMove)) {
12928             DisplayError(_("It is your turn"), 0);
12929             return;
12930         }
12931         cps = &first;
12932         break;
12933       case TwoMachinesPlay:
12934         if (WhiteOnMove(forwardMostMove) ==
12935             (first.twoMachinesColor[0] == 'w')) {
12936             cps = &first;
12937         } else {
12938             cps = &second;
12939         }
12940         break;
12941       case BeginningOfGame:
12942       default:
12943         return;
12944     }
12945     SendToProgram("?\n", cps);
12946 }
12947
12948 void
12949 TruncateGameEvent()
12950 {
12951     EditGameEvent();
12952     if (gameMode != EditGame) return;
12953     TruncateGame();
12954 }
12955
12956 void
12957 TruncateGame()
12958 {
12959     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12960     if (forwardMostMove > currentMove) {
12961         if (gameInfo.resultDetails != NULL) {
12962             free(gameInfo.resultDetails);
12963             gameInfo.resultDetails = NULL;
12964             gameInfo.result = GameUnfinished;
12965         }
12966         forwardMostMove = currentMove;
12967         HistorySet(parseList, backwardMostMove, forwardMostMove,
12968                    currentMove-1);
12969     }
12970 }
12971
12972 void
12973 HintEvent()
12974 {
12975     if (appData.noChessProgram) return;
12976     switch (gameMode) {
12977       case MachinePlaysWhite:
12978         if (WhiteOnMove(forwardMostMove)) {
12979             DisplayError(_("Wait until your turn"), 0);
12980             return;
12981         }
12982         break;
12983       case BeginningOfGame:
12984       case MachinePlaysBlack:
12985         if (!WhiteOnMove(forwardMostMove)) {
12986             DisplayError(_("Wait until your turn"), 0);
12987             return;
12988         }
12989         break;
12990       default:
12991         DisplayError(_("No hint available"), 0);
12992         return;
12993     }
12994     SendToProgram("hint\n", &first);
12995     hintRequested = TRUE;
12996 }
12997
12998 void
12999 BookEvent()
13000 {
13001     if (appData.noChessProgram) return;
13002     switch (gameMode) {
13003       case MachinePlaysWhite:
13004         if (WhiteOnMove(forwardMostMove)) {
13005             DisplayError(_("Wait until your turn"), 0);
13006             return;
13007         }
13008         break;
13009       case BeginningOfGame:
13010       case MachinePlaysBlack:
13011         if (!WhiteOnMove(forwardMostMove)) {
13012             DisplayError(_("Wait until your turn"), 0);
13013             return;
13014         }
13015         break;
13016       case EditPosition:
13017         EditPositionDone(TRUE);
13018         break;
13019       case TwoMachinesPlay:
13020         return;
13021       default:
13022         break;
13023     }
13024     SendToProgram("bk\n", &first);
13025     bookOutput[0] = NULLCHAR;
13026     bookRequested = TRUE;
13027 }
13028
13029 void
13030 AboutGameEvent()
13031 {
13032     char *tags = PGNTags(&gameInfo);
13033     TagsPopUp(tags, CmailMsg());
13034     free(tags);
13035 }
13036
13037 /* end button procedures */
13038
13039 void
13040 PrintPosition(fp, move)
13041      FILE *fp;
13042      int move;
13043 {
13044     int i, j;
13045
13046     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13047         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13048             char c = PieceToChar(boards[move][i][j]);
13049             fputc(c == 'x' ? '.' : c, fp);
13050             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13051         }
13052     }
13053     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13054       fprintf(fp, "white to play\n");
13055     else
13056       fprintf(fp, "black to play\n");
13057 }
13058
13059 void
13060 PrintOpponents(fp)
13061      FILE *fp;
13062 {
13063     if (gameInfo.white != NULL) {
13064         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13065     } else {
13066         fprintf(fp, "\n");
13067     }
13068 }
13069
13070 /* Find last component of program's own name, using some heuristics */
13071 void
13072 TidyProgramName(prog, host, buf)
13073      char *prog, *host, buf[MSG_SIZ];
13074 {
13075     char *p, *q;
13076     int local = (strcmp(host, "localhost") == 0);
13077     while (!local && (p = strchr(prog, ';')) != NULL) {
13078         p++;
13079         while (*p == ' ') p++;
13080         prog = p;
13081     }
13082     if (*prog == '"' || *prog == '\'') {
13083         q = strchr(prog + 1, *prog);
13084     } else {
13085         q = strchr(prog, ' ');
13086     }
13087     if (q == NULL) q = prog + strlen(prog);
13088     p = q;
13089     while (p >= prog && *p != '/' && *p != '\\') p--;
13090     p++;
13091     if(p == prog && *p == '"') p++;
13092     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13093     memcpy(buf, p, q - p);
13094     buf[q - p] = NULLCHAR;
13095     if (!local) {
13096         strcat(buf, "@");
13097         strcat(buf, host);
13098     }
13099 }
13100
13101 char *
13102 TimeControlTagValue()
13103 {
13104     char buf[MSG_SIZ];
13105     if (!appData.clockMode) {
13106       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13107     } else if (movesPerSession > 0) {
13108       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13109     } else if (timeIncrement == 0) {
13110       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13111     } else {
13112       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13113     }
13114     return StrSave(buf);
13115 }
13116
13117 void
13118 SetGameInfo()
13119 {
13120     /* This routine is used only for certain modes */
13121     VariantClass v = gameInfo.variant;
13122     ChessMove r = GameUnfinished;
13123     char *p = NULL;
13124
13125     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13126         r = gameInfo.result;
13127         p = gameInfo.resultDetails;
13128         gameInfo.resultDetails = NULL;
13129     }
13130     ClearGameInfo(&gameInfo);
13131     gameInfo.variant = v;
13132
13133     switch (gameMode) {
13134       case MachinePlaysWhite:
13135         gameInfo.event = StrSave( appData.pgnEventHeader );
13136         gameInfo.site = StrSave(HostName());
13137         gameInfo.date = PGNDate();
13138         gameInfo.round = StrSave("-");
13139         gameInfo.white = StrSave(first.tidy);
13140         gameInfo.black = StrSave(UserName());
13141         gameInfo.timeControl = TimeControlTagValue();
13142         break;
13143
13144       case MachinePlaysBlack:
13145         gameInfo.event = StrSave( appData.pgnEventHeader );
13146         gameInfo.site = StrSave(HostName());
13147         gameInfo.date = PGNDate();
13148         gameInfo.round = StrSave("-");
13149         gameInfo.white = StrSave(UserName());
13150         gameInfo.black = StrSave(first.tidy);
13151         gameInfo.timeControl = TimeControlTagValue();
13152         break;
13153
13154       case TwoMachinesPlay:
13155         gameInfo.event = StrSave( appData.pgnEventHeader );
13156         gameInfo.site = StrSave(HostName());
13157         gameInfo.date = PGNDate();
13158         if (matchGame > 0) {
13159             char buf[MSG_SIZ];
13160             snprintf(buf, MSG_SIZ, "%d", matchGame);
13161             gameInfo.round = StrSave(buf);
13162         } else {
13163             gameInfo.round = StrSave("-");
13164         }
13165         if (first.twoMachinesColor[0] == 'w') {
13166             gameInfo.white = StrSave(first.tidy);
13167             gameInfo.black = StrSave(second.tidy);
13168         } else {
13169             gameInfo.white = StrSave(second.tidy);
13170             gameInfo.black = StrSave(first.tidy);
13171         }
13172         gameInfo.timeControl = TimeControlTagValue();
13173         break;
13174
13175       case EditGame:
13176         gameInfo.event = StrSave("Edited game");
13177         gameInfo.site = StrSave(HostName());
13178         gameInfo.date = PGNDate();
13179         gameInfo.round = StrSave("-");
13180         gameInfo.white = StrSave("-");
13181         gameInfo.black = StrSave("-");
13182         gameInfo.result = r;
13183         gameInfo.resultDetails = p;
13184         break;
13185
13186       case EditPosition:
13187         gameInfo.event = StrSave("Edited position");
13188         gameInfo.site = StrSave(HostName());
13189         gameInfo.date = PGNDate();
13190         gameInfo.round = StrSave("-");
13191         gameInfo.white = StrSave("-");
13192         gameInfo.black = StrSave("-");
13193         break;
13194
13195       case IcsPlayingWhite:
13196       case IcsPlayingBlack:
13197       case IcsObserving:
13198       case IcsExamining:
13199         break;
13200
13201       case PlayFromGameFile:
13202         gameInfo.event = StrSave("Game from non-PGN file");
13203         gameInfo.site = StrSave(HostName());
13204         gameInfo.date = PGNDate();
13205         gameInfo.round = StrSave("-");
13206         gameInfo.white = StrSave("?");
13207         gameInfo.black = StrSave("?");
13208         break;
13209
13210       default:
13211         break;
13212     }
13213 }
13214
13215 void
13216 ReplaceComment(index, text)
13217      int index;
13218      char *text;
13219 {
13220     int len;
13221
13222     while (*text == '\n') text++;
13223     len = strlen(text);
13224     while (len > 0 && text[len - 1] == '\n') len--;
13225
13226     if (commentList[index] != NULL)
13227       free(commentList[index]);
13228
13229     if (len == 0) {
13230         commentList[index] = NULL;
13231         return;
13232     }
13233   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13234       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13235       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13236     commentList[index] = (char *) malloc(len + 2);
13237     strncpy(commentList[index], text, len);
13238     commentList[index][len] = '\n';
13239     commentList[index][len + 1] = NULLCHAR;
13240   } else {
13241     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13242     char *p;
13243     commentList[index] = (char *) malloc(len + 7);
13244     safeStrCpy(commentList[index], "{\n", 3);
13245     safeStrCpy(commentList[index]+2, text, len+1);
13246     commentList[index][len+2] = NULLCHAR;
13247     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13248     strcat(commentList[index], "\n}\n");
13249   }
13250 }
13251
13252 void
13253 CrushCRs(text)
13254      char *text;
13255 {
13256   char *p = text;
13257   char *q = text;
13258   char ch;
13259
13260   do {
13261     ch = *p++;
13262     if (ch == '\r') continue;
13263     *q++ = ch;
13264   } while (ch != '\0');
13265 }
13266
13267 void
13268 AppendComment(index, text, addBraces)
13269      int index;
13270      char *text;
13271      Boolean addBraces; // [HGM] braces: tells if we should add {}
13272 {
13273     int oldlen, len;
13274     char *old;
13275
13276 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13277     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13278
13279     CrushCRs(text);
13280     while (*text == '\n') text++;
13281     len = strlen(text);
13282     while (len > 0 && text[len - 1] == '\n') len--;
13283
13284     if (len == 0) return;
13285
13286     if (commentList[index] != NULL) {
13287         old = commentList[index];
13288         oldlen = strlen(old);
13289         while(commentList[index][oldlen-1] ==  '\n')
13290           commentList[index][--oldlen] = NULLCHAR;
13291         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13292         safeStrCpy(commentList[index], old, oldlen);
13293         free(old);
13294         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13295         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13296           if(addBraces) addBraces = FALSE; else { text++; len--; }
13297           while (*text == '\n') { text++; len--; }
13298           commentList[index][--oldlen] = NULLCHAR;
13299       }
13300         if(addBraces) strcat(commentList[index], "\n{\n");
13301         else          strcat(commentList[index], "\n");
13302         strcat(commentList[index], text);
13303         if(addBraces) strcat(commentList[index], "\n}\n");
13304         else          strcat(commentList[index], "\n");
13305     } else {
13306         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13307         if(addBraces)
13308           safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
13309         else commentList[index][0] = NULLCHAR;
13310         strcat(commentList[index], text);
13311         strcat(commentList[index], "\n");
13312         if(addBraces) strcat(commentList[index], "}\n");
13313     }
13314 }
13315
13316 static char * FindStr( char * text, char * sub_text )
13317 {
13318     char * result = strstr( text, sub_text );
13319
13320     if( result != NULL ) {
13321         result += strlen( sub_text );
13322     }
13323
13324     return result;
13325 }
13326
13327 /* [AS] Try to extract PV info from PGN comment */
13328 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13329 char *GetInfoFromComment( int index, char * text )
13330 {
13331     char * sep = text;
13332
13333     if( text != NULL && index > 0 ) {
13334         int score = 0;
13335         int depth = 0;
13336         int time = -1, sec = 0, deci;
13337         char * s_eval = FindStr( text, "[%eval " );
13338         char * s_emt = FindStr( text, "[%emt " );
13339
13340         if( s_eval != NULL || s_emt != NULL ) {
13341             /* New style */
13342             char delim;
13343
13344             if( s_eval != NULL ) {
13345                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13346                     return text;
13347                 }
13348
13349                 if( delim != ']' ) {
13350                     return text;
13351                 }
13352             }
13353
13354             if( s_emt != NULL ) {
13355             }
13356                 return text;
13357         }
13358         else {
13359             /* We expect something like: [+|-]nnn.nn/dd */
13360             int score_lo = 0;
13361
13362             if(*text != '{') return text; // [HGM] braces: must be normal comment
13363
13364             sep = strchr( text, '/' );
13365             if( sep == NULL || sep < (text+4) ) {
13366                 return text;
13367             }
13368
13369             time = -1; sec = -1; deci = -1;
13370             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13371                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13372                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13373                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13374                 return text;
13375             }
13376
13377             if( score_lo < 0 || score_lo >= 100 ) {
13378                 return text;
13379             }
13380
13381             if(sec >= 0) time = 600*time + 10*sec; else
13382             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13383
13384             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13385
13386             /* [HGM] PV time: now locate end of PV info */
13387             while( *++sep >= '0' && *sep <= '9'); // strip depth
13388             if(time >= 0)
13389             while( *++sep >= '0' && *sep <= '9'); // strip time
13390             if(sec >= 0)
13391             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13392             if(deci >= 0)
13393             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13394             while(*sep == ' ') sep++;
13395         }
13396
13397         if( depth <= 0 ) {
13398             return text;
13399         }
13400
13401         if( time < 0 ) {
13402             time = -1;
13403         }
13404
13405         pvInfoList[index-1].depth = depth;
13406         pvInfoList[index-1].score = score;
13407         pvInfoList[index-1].time  = 10*time; // centi-sec
13408         if(*sep == '}') *sep = 0; else *--sep = '{';
13409     }
13410     return sep;
13411 }
13412
13413 void
13414 SendToProgram(message, cps)
13415      char *message;
13416      ChessProgramState *cps;
13417 {
13418     int count, outCount, error;
13419     char buf[MSG_SIZ];
13420
13421     if (cps->pr == NULL) return;
13422     Attention(cps);
13423
13424     if (appData.debugMode) {
13425         TimeMark now;
13426         GetTimeMark(&now);
13427         fprintf(debugFP, "%ld >%-6s: %s",
13428                 SubtractTimeMarks(&now, &programStartTime),
13429                 cps->which, message);
13430     }
13431
13432     count = strlen(message);
13433     outCount = OutputToProcess(cps->pr, message, count, &error);
13434     if (outCount < count && !exiting
13435                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13436       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13437         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13438             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13439                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13440                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13441             } else {
13442                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13443             }
13444             gameInfo.resultDetails = StrSave(buf);
13445         }
13446         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13447     }
13448 }
13449
13450 void
13451 ReceiveFromProgram(isr, closure, message, count, error)
13452      InputSourceRef isr;
13453      VOIDSTAR closure;
13454      char *message;
13455      int count;
13456      int error;
13457 {
13458     char *end_str;
13459     char buf[MSG_SIZ];
13460     ChessProgramState *cps = (ChessProgramState *)closure;
13461
13462     if (isr != cps->isr) return; /* Killed intentionally */
13463     if (count <= 0) {
13464         if (count == 0) {
13465             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13466                     cps->which, cps->program);
13467         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13468                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13469                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13470                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13471                 } else {
13472                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13473                 }
13474                 gameInfo.resultDetails = StrSave(buf);
13475             }
13476             RemoveInputSource(cps->isr);
13477             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13478         } else {
13479             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13480                     cps->which, cps->program);
13481             RemoveInputSource(cps->isr);
13482
13483             /* [AS] Program is misbehaving badly... kill it */
13484             if( count == -2 ) {
13485                 DestroyChildProcess( cps->pr, 9 );
13486                 cps->pr = NoProc;
13487             }
13488
13489             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13490         }
13491         return;
13492     }
13493
13494     if ((end_str = strchr(message, '\r')) != NULL)
13495       *end_str = NULLCHAR;
13496     if ((end_str = strchr(message, '\n')) != NULL)
13497       *end_str = NULLCHAR;
13498
13499     if (appData.debugMode) {
13500         TimeMark now; int print = 1;
13501         char *quote = ""; char c; int i;
13502
13503         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13504                 char start = message[0];
13505                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13506                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13507                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13508                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13509                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13510                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13511                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13512                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13513                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13514                     print = (appData.engineComments >= 2);
13515                 }
13516                 message[0] = start; // restore original message
13517         }
13518         if(print) {
13519                 GetTimeMark(&now);
13520                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13521                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13522                         quote,
13523                         message);
13524         }
13525     }
13526
13527     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13528     if (appData.icsEngineAnalyze) {
13529         if (strstr(message, "whisper") != NULL ||
13530              strstr(message, "kibitz") != NULL ||
13531             strstr(message, "tellics") != NULL) return;
13532     }
13533
13534     HandleMachineMove(message, cps);
13535 }
13536
13537
13538 void
13539 SendTimeControl(cps, mps, tc, inc, sd, st)
13540      ChessProgramState *cps;
13541      int mps, inc, sd, st;
13542      long tc;
13543 {
13544     char buf[MSG_SIZ];
13545     int seconds;
13546
13547     if( timeControl_2 > 0 ) {
13548         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13549             tc = timeControl_2;
13550         }
13551     }
13552     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13553     inc /= cps->timeOdds;
13554     st  /= cps->timeOdds;
13555
13556     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13557
13558     if (st > 0) {
13559       /* Set exact time per move, normally using st command */
13560       if (cps->stKludge) {
13561         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13562         seconds = st % 60;
13563         if (seconds == 0) {
13564           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13565         } else {
13566           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13567         }
13568       } else {
13569         snprintf(buf, MSG_SIZ, "st %d\n", st);
13570       }
13571     } else {
13572       /* Set conventional or incremental time control, using level command */
13573       if (seconds == 0) {
13574         /* Note old gnuchess bug -- minutes:seconds used to not work.
13575            Fixed in later versions, but still avoid :seconds
13576            when seconds is 0. */
13577         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13578       } else {
13579         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13580                  seconds, inc/1000.);
13581       }
13582     }
13583     SendToProgram(buf, cps);
13584
13585     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13586     /* Orthogonally, limit search to given depth */
13587     if (sd > 0) {
13588       if (cps->sdKludge) {
13589         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13590       } else {
13591         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13592       }
13593       SendToProgram(buf, cps);
13594     }
13595
13596     if(cps->nps > 0) { /* [HGM] nps */
13597         if(cps->supportsNPS == FALSE)
13598           cps->nps = -1; // don't use if engine explicitly says not supported!
13599         else {
13600           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13601           SendToProgram(buf, cps);
13602         }
13603     }
13604 }
13605
13606 ChessProgramState *WhitePlayer()
13607 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13608 {
13609     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13610        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13611         return &second;
13612     return &first;
13613 }
13614
13615 void
13616 SendTimeRemaining(cps, machineWhite)
13617      ChessProgramState *cps;
13618      int /*boolean*/ machineWhite;
13619 {
13620     char message[MSG_SIZ];
13621     long time, otime;
13622
13623     /* Note: this routine must be called when the clocks are stopped
13624        or when they have *just* been set or switched; otherwise
13625        it will be off by the time since the current tick started.
13626     */
13627     if (machineWhite) {
13628         time = whiteTimeRemaining / 10;
13629         otime = blackTimeRemaining / 10;
13630     } else {
13631         time = blackTimeRemaining / 10;
13632         otime = whiteTimeRemaining / 10;
13633     }
13634     /* [HGM] translate opponent's time by time-odds factor */
13635     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13636     if (appData.debugMode) {
13637         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13638     }
13639
13640     if (time <= 0) time = 1;
13641     if (otime <= 0) otime = 1;
13642
13643     snprintf(message, MSG_SIZ, "time %ld\n", time);
13644     SendToProgram(message, cps);
13645
13646     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13647     SendToProgram(message, cps);
13648 }
13649
13650 int
13651 BoolFeature(p, name, loc, cps)
13652      char **p;
13653      char *name;
13654      int *loc;
13655      ChessProgramState *cps;
13656 {
13657   char buf[MSG_SIZ];
13658   int len = strlen(name);
13659   int val;
13660
13661   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13662     (*p) += len + 1;
13663     sscanf(*p, "%d", &val);
13664     *loc = (val != 0);
13665     while (**p && **p != ' ')
13666       (*p)++;
13667     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13668     SendToProgram(buf, cps);
13669     return TRUE;
13670   }
13671   return FALSE;
13672 }
13673
13674 int
13675 IntFeature(p, name, loc, cps)
13676      char **p;
13677      char *name;
13678      int *loc;
13679      ChessProgramState *cps;
13680 {
13681   char buf[MSG_SIZ];
13682   int len = strlen(name);
13683   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13684     (*p) += len + 1;
13685     sscanf(*p, "%d", loc);
13686     while (**p && **p != ' ') (*p)++;
13687     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13688     SendToProgram(buf, cps);
13689     return TRUE;
13690   }
13691   return FALSE;
13692 }
13693
13694 int
13695 StringFeature(p, name, loc, cps)
13696      char **p;
13697      char *name;
13698      char loc[];
13699      ChessProgramState *cps;
13700 {
13701   char buf[MSG_SIZ];
13702   int len = strlen(name);
13703   if (strncmp((*p), name, len) == 0
13704       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13705     (*p) += len + 2;
13706     sscanf(*p, "%[^\"]", loc);
13707     while (**p && **p != '\"') (*p)++;
13708     if (**p == '\"') (*p)++;
13709     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13710     SendToProgram(buf, cps);
13711     return TRUE;
13712   }
13713   return FALSE;
13714 }
13715
13716 int
13717 ParseOption(Option *opt, ChessProgramState *cps)
13718 // [HGM] options: process the string that defines an engine option, and determine
13719 // name, type, default value, and allowed value range
13720 {
13721         char *p, *q, buf[MSG_SIZ];
13722         int n, min = (-1)<<31, max = 1<<31, def;
13723
13724         if(p = strstr(opt->name, " -spin ")) {
13725             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13726             if(max < min) max = min; // enforce consistency
13727             if(def < min) def = min;
13728             if(def > max) def = max;
13729             opt->value = def;
13730             opt->min = min;
13731             opt->max = max;
13732             opt->type = Spin;
13733         } else if((p = strstr(opt->name, " -slider "))) {
13734             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13735             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13736             if(max < min) max = min; // enforce consistency
13737             if(def < min) def = min;
13738             if(def > max) def = max;
13739             opt->value = def;
13740             opt->min = min;
13741             opt->max = max;
13742             opt->type = Spin; // Slider;
13743         } else if((p = strstr(opt->name, " -string "))) {
13744             opt->textValue = p+9;
13745             opt->type = TextBox;
13746         } else if((p = strstr(opt->name, " -file "))) {
13747             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13748             opt->textValue = p+7;
13749             opt->type = TextBox; // FileName;
13750         } else if((p = strstr(opt->name, " -path "))) {
13751             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13752             opt->textValue = p+7;
13753             opt->type = TextBox; // PathName;
13754         } else if(p = strstr(opt->name, " -check ")) {
13755             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13756             opt->value = (def != 0);
13757             opt->type = CheckBox;
13758         } else if(p = strstr(opt->name, " -combo ")) {
13759             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13760             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13761             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13762             opt->value = n = 0;
13763             while(q = StrStr(q, " /// ")) {
13764                 n++; *q = 0;    // count choices, and null-terminate each of them
13765                 q += 5;
13766                 if(*q == '*') { // remember default, which is marked with * prefix
13767                     q++;
13768                     opt->value = n;
13769                 }
13770                 cps->comboList[cps->comboCnt++] = q;
13771             }
13772             cps->comboList[cps->comboCnt++] = NULL;
13773             opt->max = n + 1;
13774             opt->type = ComboBox;
13775         } else if(p = strstr(opt->name, " -button")) {
13776             opt->type = Button;
13777         } else if(p = strstr(opt->name, " -save")) {
13778             opt->type = SaveButton;
13779         } else return FALSE;
13780         *p = 0; // terminate option name
13781         // now look if the command-line options define a setting for this engine option.
13782         if(cps->optionSettings && cps->optionSettings[0])
13783             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13784         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13785           snprintf(buf, MSG_SIZ, "option %s", p);
13786                 if(p = strstr(buf, ",")) *p = 0;
13787                 strcat(buf, "\n");
13788                 SendToProgram(buf, cps);
13789         }
13790         return TRUE;
13791 }
13792
13793 void
13794 FeatureDone(cps, val)
13795      ChessProgramState* cps;
13796      int val;
13797 {
13798   DelayedEventCallback cb = GetDelayedEvent();
13799   if ((cb == InitBackEnd3 && cps == &first) ||
13800       (cb == TwoMachinesEventIfReady && cps == &second)) {
13801     CancelDelayedEvent();
13802     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13803   }
13804   cps->initDone = val;
13805 }
13806
13807 /* Parse feature command from engine */
13808 void
13809 ParseFeatures(args, cps)
13810      char* args;
13811      ChessProgramState *cps;
13812 {
13813   char *p = args;
13814   char *q;
13815   int val;
13816   char buf[MSG_SIZ];
13817
13818   for (;;) {
13819     while (*p == ' ') p++;
13820     if (*p == NULLCHAR) return;
13821
13822     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13823     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13824     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13825     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13826     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13827     if (BoolFeature(&p, "reuse", &val, cps)) {
13828       /* Engine can disable reuse, but can't enable it if user said no */
13829       if (!val) cps->reuse = FALSE;
13830       continue;
13831     }
13832     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13833     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13834       if (gameMode == TwoMachinesPlay) {
13835         DisplayTwoMachinesTitle();
13836       } else {
13837         DisplayTitle("");
13838       }
13839       continue;
13840     }
13841     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13842     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13843     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13844     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13845     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13846     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13847     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13848     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13849     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13850     if (IntFeature(&p, "done", &val, cps)) {
13851       FeatureDone(cps, val);
13852       continue;
13853     }
13854     /* Added by Tord: */
13855     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13856     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13857     /* End of additions by Tord */
13858
13859     /* [HGM] added features: */
13860     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13861     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13862     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13863     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13864     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13865     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13866     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13867         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13868           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13869             SendToProgram(buf, cps);
13870             continue;
13871         }
13872         if(cps->nrOptions >= MAX_OPTIONS) {
13873             cps->nrOptions--;
13874             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13875             DisplayError(buf, 0);
13876         }
13877         continue;
13878     }
13879     /* End of additions by HGM */
13880
13881     /* unknown feature: complain and skip */
13882     q = p;
13883     while (*q && *q != '=') q++;
13884     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13885     SendToProgram(buf, cps);
13886     p = q;
13887     if (*p == '=') {
13888       p++;
13889       if (*p == '\"') {
13890         p++;
13891         while (*p && *p != '\"') p++;
13892         if (*p == '\"') p++;
13893       } else {
13894         while (*p && *p != ' ') p++;
13895       }
13896     }
13897   }
13898
13899 }
13900
13901 void
13902 PeriodicUpdatesEvent(newState)
13903      int newState;
13904 {
13905     if (newState == appData.periodicUpdates)
13906       return;
13907
13908     appData.periodicUpdates=newState;
13909
13910     /* Display type changes, so update it now */
13911 //    DisplayAnalysis();
13912
13913     /* Get the ball rolling again... */
13914     if (newState) {
13915         AnalysisPeriodicEvent(1);
13916         StartAnalysisClock();
13917     }
13918 }
13919
13920 void
13921 PonderNextMoveEvent(newState)
13922      int newState;
13923 {
13924     if (newState == appData.ponderNextMove) return;
13925     if (gameMode == EditPosition) EditPositionDone(TRUE);
13926     if (newState) {
13927         SendToProgram("hard\n", &first);
13928         if (gameMode == TwoMachinesPlay) {
13929             SendToProgram("hard\n", &second);
13930         }
13931     } else {
13932         SendToProgram("easy\n", &first);
13933         thinkOutput[0] = NULLCHAR;
13934         if (gameMode == TwoMachinesPlay) {
13935             SendToProgram("easy\n", &second);
13936         }
13937     }
13938     appData.ponderNextMove = newState;
13939 }
13940
13941 void
13942 NewSettingEvent(option, feature, command, value)
13943      char *command;
13944      int option, value, *feature;
13945 {
13946     char buf[MSG_SIZ];
13947
13948     if (gameMode == EditPosition) EditPositionDone(TRUE);
13949     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
13950     if(feature == NULL || *feature) SendToProgram(buf, &first);
13951     if (gameMode == TwoMachinesPlay) {
13952         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13953     }
13954 }
13955
13956 void
13957 ShowThinkingEvent()
13958 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13959 {
13960     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13961     int newState = appData.showThinking
13962         // [HGM] thinking: other features now need thinking output as well
13963         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13964
13965     if (oldState == newState) return;
13966     oldState = newState;
13967     if (gameMode == EditPosition) EditPositionDone(TRUE);
13968     if (oldState) {
13969         SendToProgram("post\n", &first);
13970         if (gameMode == TwoMachinesPlay) {
13971             SendToProgram("post\n", &second);
13972         }
13973     } else {
13974         SendToProgram("nopost\n", &first);
13975         thinkOutput[0] = NULLCHAR;
13976         if (gameMode == TwoMachinesPlay) {
13977             SendToProgram("nopost\n", &second);
13978         }
13979     }
13980 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13981 }
13982
13983 void
13984 AskQuestionEvent(title, question, replyPrefix, which)
13985      char *title; char *question; char *replyPrefix; char *which;
13986 {
13987   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13988   if (pr == NoProc) return;
13989   AskQuestion(title, question, replyPrefix, pr);
13990 }
13991
13992 void
13993 DisplayMove(moveNumber)
13994      int moveNumber;
13995 {
13996     char message[MSG_SIZ];
13997     char res[MSG_SIZ];
13998     char cpThinkOutput[MSG_SIZ];
13999
14000     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14001
14002     if (moveNumber == forwardMostMove - 1 ||
14003         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14004
14005         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14006
14007         if (strchr(cpThinkOutput, '\n')) {
14008             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14009         }
14010     } else {
14011         *cpThinkOutput = NULLCHAR;
14012     }
14013
14014     /* [AS] Hide thinking from human user */
14015     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14016         *cpThinkOutput = NULLCHAR;
14017         if( thinkOutput[0] != NULLCHAR ) {
14018             int i;
14019
14020             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14021                 cpThinkOutput[i] = '.';
14022             }
14023             cpThinkOutput[i] = NULLCHAR;
14024             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14025         }
14026     }
14027
14028     if (moveNumber == forwardMostMove - 1 &&
14029         gameInfo.resultDetails != NULL) {
14030         if (gameInfo.resultDetails[0] == NULLCHAR) {
14031           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14032         } else {
14033           snprintf(res, MSG_SIZ, " {%s} %s",
14034                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14035         }
14036     } else {
14037         res[0] = NULLCHAR;
14038     }
14039
14040     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14041         DisplayMessage(res, cpThinkOutput);
14042     } else {
14043       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14044                 WhiteOnMove(moveNumber) ? " " : ".. ",
14045                 parseList[moveNumber], res);
14046         DisplayMessage(message, cpThinkOutput);
14047     }
14048 }
14049
14050 void
14051 DisplayComment(moveNumber, text)
14052      int moveNumber;
14053      char *text;
14054 {
14055     char title[MSG_SIZ];
14056     char buf[8000]; // comment can be long!
14057     int score, depth;
14058
14059     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14060       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14061     } else {
14062       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14063               WhiteOnMove(moveNumber) ? " " : ".. ",
14064               parseList[moveNumber]);
14065     }
14066     // [HGM] PV info: display PV info together with (or as) comment
14067     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14068       if(text == NULL) text = "";
14069       score = pvInfoList[moveNumber].score;
14070       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14071               depth, (pvInfoList[moveNumber].time+50)/100, text);
14072       text = buf;
14073     }
14074     if (text != NULL && (appData.autoDisplayComment || commentUp))
14075         CommentPopUp(title, text);
14076 }
14077
14078 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14079  * might be busy thinking or pondering.  It can be omitted if your
14080  * gnuchess is configured to stop thinking immediately on any user
14081  * input.  However, that gnuchess feature depends on the FIONREAD
14082  * ioctl, which does not work properly on some flavors of Unix.
14083  */
14084 void
14085 Attention(cps)
14086      ChessProgramState *cps;
14087 {
14088 #if ATTENTION
14089     if (!cps->useSigint) return;
14090     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14091     switch (gameMode) {
14092       case MachinePlaysWhite:
14093       case MachinePlaysBlack:
14094       case TwoMachinesPlay:
14095       case IcsPlayingWhite:
14096       case IcsPlayingBlack:
14097       case AnalyzeMode:
14098       case AnalyzeFile:
14099         /* Skip if we know it isn't thinking */
14100         if (!cps->maybeThinking) return;
14101         if (appData.debugMode)
14102           fprintf(debugFP, "Interrupting %s\n", cps->which);
14103         InterruptChildProcess(cps->pr);
14104         cps->maybeThinking = FALSE;
14105         break;
14106       default:
14107         break;
14108     }
14109 #endif /*ATTENTION*/
14110 }
14111
14112 int
14113 CheckFlags()
14114 {
14115     if (whiteTimeRemaining <= 0) {
14116         if (!whiteFlag) {
14117             whiteFlag = TRUE;
14118             if (appData.icsActive) {
14119                 if (appData.autoCallFlag &&
14120                     gameMode == IcsPlayingBlack && !blackFlag) {
14121                   SendToICS(ics_prefix);
14122                   SendToICS("flag\n");
14123                 }
14124             } else {
14125                 if (blackFlag) {
14126                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14127                 } else {
14128                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14129                     if (appData.autoCallFlag) {
14130                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14131                         return TRUE;
14132                     }
14133                 }
14134             }
14135         }
14136     }
14137     if (blackTimeRemaining <= 0) {
14138         if (!blackFlag) {
14139             blackFlag = TRUE;
14140             if (appData.icsActive) {
14141                 if (appData.autoCallFlag &&
14142                     gameMode == IcsPlayingWhite && !whiteFlag) {
14143                   SendToICS(ics_prefix);
14144                   SendToICS("flag\n");
14145                 }
14146             } else {
14147                 if (whiteFlag) {
14148                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14149                 } else {
14150                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14151                     if (appData.autoCallFlag) {
14152                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14153                         return TRUE;
14154                     }
14155                 }
14156             }
14157         }
14158     }
14159     return FALSE;
14160 }
14161
14162 void
14163 CheckTimeControl()
14164 {
14165     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14166         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14167
14168     /*
14169      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14170      */
14171     if ( !WhiteOnMove(forwardMostMove) ) {
14172         /* White made time control */
14173         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14174         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14175         /* [HGM] time odds: correct new time quota for time odds! */
14176                                             / WhitePlayer()->timeOdds;
14177         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14178     } else {
14179         lastBlack -= blackTimeRemaining;
14180         /* Black made time control */
14181         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14182                                             / WhitePlayer()->other->timeOdds;
14183         lastWhite = whiteTimeRemaining;
14184     }
14185 }
14186
14187 void
14188 DisplayBothClocks()
14189 {
14190     int wom = gameMode == EditPosition ?
14191       !blackPlaysFirst : WhiteOnMove(currentMove);
14192     DisplayWhiteClock(whiteTimeRemaining, wom);
14193     DisplayBlackClock(blackTimeRemaining, !wom);
14194 }
14195
14196
14197 /* Timekeeping seems to be a portability nightmare.  I think everyone
14198    has ftime(), but I'm really not sure, so I'm including some ifdefs
14199    to use other calls if you don't.  Clocks will be less accurate if
14200    you have neither ftime nor gettimeofday.
14201 */
14202
14203 /* VS 2008 requires the #include outside of the function */
14204 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14205 #include <sys/timeb.h>
14206 #endif
14207
14208 /* Get the current time as a TimeMark */
14209 void
14210 GetTimeMark(tm)
14211      TimeMark *tm;
14212 {
14213 #if HAVE_GETTIMEOFDAY
14214
14215     struct timeval timeVal;
14216     struct timezone timeZone;
14217
14218     gettimeofday(&timeVal, &timeZone);
14219     tm->sec = (long) timeVal.tv_sec;
14220     tm->ms = (int) (timeVal.tv_usec / 1000L);
14221
14222 #else /*!HAVE_GETTIMEOFDAY*/
14223 #if HAVE_FTIME
14224
14225 // include <sys/timeb.h> / moved to just above start of function
14226     struct timeb timeB;
14227
14228     ftime(&timeB);
14229     tm->sec = (long) timeB.time;
14230     tm->ms = (int) timeB.millitm;
14231
14232 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14233     tm->sec = (long) time(NULL);
14234     tm->ms = 0;
14235 #endif
14236 #endif
14237 }
14238
14239 /* Return the difference in milliseconds between two
14240    time marks.  We assume the difference will fit in a long!
14241 */
14242 long
14243 SubtractTimeMarks(tm2, tm1)
14244      TimeMark *tm2, *tm1;
14245 {
14246     return 1000L*(tm2->sec - tm1->sec) +
14247            (long) (tm2->ms - tm1->ms);
14248 }
14249
14250
14251 /*
14252  * Code to manage the game clocks.
14253  *
14254  * In tournament play, black starts the clock and then white makes a move.
14255  * We give the human user a slight advantage if he is playing white---the
14256  * clocks don't run until he makes his first move, so it takes zero time.
14257  * Also, we don't account for network lag, so we could get out of sync
14258  * with GNU Chess's clock -- but then, referees are always right.
14259  */
14260
14261 static TimeMark tickStartTM;
14262 static long intendedTickLength;
14263
14264 long
14265 NextTickLength(timeRemaining)
14266      long timeRemaining;
14267 {
14268     long nominalTickLength, nextTickLength;
14269
14270     if (timeRemaining > 0L && timeRemaining <= 10000L)
14271       nominalTickLength = 100L;
14272     else
14273       nominalTickLength = 1000L;
14274     nextTickLength = timeRemaining % nominalTickLength;
14275     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14276
14277     return nextTickLength;
14278 }
14279
14280 /* Adjust clock one minute up or down */
14281 void
14282 AdjustClock(Boolean which, int dir)
14283 {
14284     if(which) blackTimeRemaining += 60000*dir;
14285     else      whiteTimeRemaining += 60000*dir;
14286     DisplayBothClocks();
14287 }
14288
14289 /* Stop clocks and reset to a fresh time control */
14290 void
14291 ResetClocks()
14292 {
14293     (void) StopClockTimer();
14294     if (appData.icsActive) {
14295         whiteTimeRemaining = blackTimeRemaining = 0;
14296     } else if (searchTime) {
14297         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14298         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14299     } else { /* [HGM] correct new time quote for time odds */
14300         whiteTC = blackTC = fullTimeControlString;
14301         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14302         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14303     }
14304     if (whiteFlag || blackFlag) {
14305         DisplayTitle("");
14306         whiteFlag = blackFlag = FALSE;
14307     }
14308     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14309     DisplayBothClocks();
14310 }
14311
14312 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14313
14314 /* Decrement running clock by amount of time that has passed */
14315 void
14316 DecrementClocks()
14317 {
14318     long timeRemaining;
14319     long lastTickLength, fudge;
14320     TimeMark now;
14321
14322     if (!appData.clockMode) return;
14323     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14324
14325     GetTimeMark(&now);
14326
14327     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14328
14329     /* Fudge if we woke up a little too soon */
14330     fudge = intendedTickLength - lastTickLength;
14331     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14332
14333     if (WhiteOnMove(forwardMostMove)) {
14334         if(whiteNPS >= 0) lastTickLength = 0;
14335         timeRemaining = whiteTimeRemaining -= lastTickLength;
14336         if(timeRemaining < 0 && !appData.icsActive) {
14337             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14338             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14339                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14340                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14341             }
14342         }
14343         DisplayWhiteClock(whiteTimeRemaining - fudge,
14344                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14345     } else {
14346         if(blackNPS >= 0) lastTickLength = 0;
14347         timeRemaining = blackTimeRemaining -= lastTickLength;
14348         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14349             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14350             if(suddenDeath) {
14351                 blackStartMove = forwardMostMove;
14352                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14353             }
14354         }
14355         DisplayBlackClock(blackTimeRemaining - fudge,
14356                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14357     }
14358     if (CheckFlags()) return;
14359
14360     tickStartTM = now;
14361     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14362     StartClockTimer(intendedTickLength);
14363
14364     /* if the time remaining has fallen below the alarm threshold, sound the
14365      * alarm. if the alarm has sounded and (due to a takeback or time control
14366      * with increment) the time remaining has increased to a level above the
14367      * threshold, reset the alarm so it can sound again.
14368      */
14369
14370     if (appData.icsActive && appData.icsAlarm) {
14371
14372         /* make sure we are dealing with the user's clock */
14373         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14374                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14375            )) return;
14376
14377         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14378             alarmSounded = FALSE;
14379         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14380             PlayAlarmSound();
14381             alarmSounded = TRUE;
14382         }
14383     }
14384 }
14385
14386
14387 /* A player has just moved, so stop the previously running
14388    clock and (if in clock mode) start the other one.
14389    We redisplay both clocks in case we're in ICS mode, because
14390    ICS gives us an update to both clocks after every move.
14391    Note that this routine is called *after* forwardMostMove
14392    is updated, so the last fractional tick must be subtracted
14393    from the color that is *not* on move now.
14394 */
14395 void
14396 SwitchClocks(int newMoveNr)
14397 {
14398     long lastTickLength;
14399     TimeMark now;
14400     int flagged = FALSE;
14401
14402     GetTimeMark(&now);
14403
14404     if (StopClockTimer() && appData.clockMode) {
14405         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14406         if (!WhiteOnMove(forwardMostMove)) {
14407             if(blackNPS >= 0) lastTickLength = 0;
14408             blackTimeRemaining -= lastTickLength;
14409            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14410 //         if(pvInfoList[forwardMostMove-1].time == -1)
14411                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14412                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14413         } else {
14414            if(whiteNPS >= 0) lastTickLength = 0;
14415            whiteTimeRemaining -= lastTickLength;
14416            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14417 //         if(pvInfoList[forwardMostMove-1].time == -1)
14418                  pvInfoList[forwardMostMove-1].time =
14419                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14420         }
14421         flagged = CheckFlags();
14422     }
14423     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14424     CheckTimeControl();
14425
14426     if (flagged || !appData.clockMode) return;
14427
14428     switch (gameMode) {
14429       case MachinePlaysBlack:
14430       case MachinePlaysWhite:
14431       case BeginningOfGame:
14432         if (pausing) return;
14433         break;
14434
14435       case EditGame:
14436       case PlayFromGameFile:
14437       case IcsExamining:
14438         return;
14439
14440       default:
14441         break;
14442     }
14443
14444     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14445         if(WhiteOnMove(forwardMostMove))
14446              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14447         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14448     }
14449
14450     tickStartTM = now;
14451     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14452       whiteTimeRemaining : blackTimeRemaining);
14453     StartClockTimer(intendedTickLength);
14454 }
14455
14456
14457 /* Stop both clocks */
14458 void
14459 StopClocks()
14460 {
14461     long lastTickLength;
14462     TimeMark now;
14463
14464     if (!StopClockTimer()) return;
14465     if (!appData.clockMode) return;
14466
14467     GetTimeMark(&now);
14468
14469     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14470     if (WhiteOnMove(forwardMostMove)) {
14471         if(whiteNPS >= 0) lastTickLength = 0;
14472         whiteTimeRemaining -= lastTickLength;
14473         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14474     } else {
14475         if(blackNPS >= 0) lastTickLength = 0;
14476         blackTimeRemaining -= lastTickLength;
14477         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14478     }
14479     CheckFlags();
14480 }
14481
14482 /* Start clock of player on move.  Time may have been reset, so
14483    if clock is already running, stop and restart it. */
14484 void
14485 StartClocks()
14486 {
14487     (void) StopClockTimer(); /* in case it was running already */
14488     DisplayBothClocks();
14489     if (CheckFlags()) return;
14490
14491     if (!appData.clockMode) return;
14492     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14493
14494     GetTimeMark(&tickStartTM);
14495     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14496       whiteTimeRemaining : blackTimeRemaining);
14497
14498    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14499     whiteNPS = blackNPS = -1;
14500     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14501        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14502         whiteNPS = first.nps;
14503     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14504        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14505         blackNPS = first.nps;
14506     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14507         whiteNPS = second.nps;
14508     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14509         blackNPS = second.nps;
14510     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14511
14512     StartClockTimer(intendedTickLength);
14513 }
14514
14515 char *
14516 TimeString(ms)
14517      long ms;
14518 {
14519     long second, minute, hour, day;
14520     char *sign = "";
14521     static char buf[32];
14522
14523     if (ms > 0 && ms <= 9900) {
14524       /* convert milliseconds to tenths, rounding up */
14525       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14526
14527       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14528       return buf;
14529     }
14530
14531     /* convert milliseconds to seconds, rounding up */
14532     /* use floating point to avoid strangeness of integer division
14533        with negative dividends on many machines */
14534     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14535
14536     if (second < 0) {
14537         sign = "-";
14538         second = -second;
14539     }
14540
14541     day = second / (60 * 60 * 24);
14542     second = second % (60 * 60 * 24);
14543     hour = second / (60 * 60);
14544     second = second % (60 * 60);
14545     minute = second / 60;
14546     second = second % 60;
14547
14548     if (day > 0)
14549       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14550               sign, day, hour, minute, second);
14551     else if (hour > 0)
14552       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14553     else
14554       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14555
14556     return buf;
14557 }
14558
14559
14560 /*
14561  * This is necessary because some C libraries aren't ANSI C compliant yet.
14562  */
14563 char *
14564 StrStr(string, match)
14565      char *string, *match;
14566 {
14567     int i, length;
14568
14569     length = strlen(match);
14570
14571     for (i = strlen(string) - length; i >= 0; i--, string++)
14572       if (!strncmp(match, string, length))
14573         return string;
14574
14575     return NULL;
14576 }
14577
14578 char *
14579 StrCaseStr(string, match)
14580      char *string, *match;
14581 {
14582     int i, j, length;
14583
14584     length = strlen(match);
14585
14586     for (i = strlen(string) - length; i >= 0; i--, string++) {
14587         for (j = 0; j < length; j++) {
14588             if (ToLower(match[j]) != ToLower(string[j]))
14589               break;
14590         }
14591         if (j == length) return string;
14592     }
14593
14594     return NULL;
14595 }
14596
14597 #ifndef _amigados
14598 int
14599 StrCaseCmp(s1, s2)
14600      char *s1, *s2;
14601 {
14602     char c1, c2;
14603
14604     for (;;) {
14605         c1 = ToLower(*s1++);
14606         c2 = ToLower(*s2++);
14607         if (c1 > c2) return 1;
14608         if (c1 < c2) return -1;
14609         if (c1 == NULLCHAR) return 0;
14610     }
14611 }
14612
14613
14614 int
14615 ToLower(c)
14616      int c;
14617 {
14618     return isupper(c) ? tolower(c) : c;
14619 }
14620
14621
14622 int
14623 ToUpper(c)
14624      int c;
14625 {
14626     return islower(c) ? toupper(c) : c;
14627 }
14628 #endif /* !_amigados    */
14629
14630 char *
14631 StrSave(s)
14632      char *s;
14633 {
14634   char *ret;
14635
14636   if ((ret = (char *) malloc(strlen(s) + 1)))
14637     {
14638       safeStrCpy(ret, s, strlen(s)+1);
14639     }
14640   return ret;
14641 }
14642
14643 char *
14644 StrSavePtr(s, savePtr)
14645      char *s, **savePtr;
14646 {
14647     if (*savePtr) {
14648         free(*savePtr);
14649     }
14650     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14651       safeStrCpy(*savePtr, s, strlen(s)+1);
14652     }
14653     return(*savePtr);
14654 }
14655
14656 char *
14657 PGNDate()
14658 {
14659     time_t clock;
14660     struct tm *tm;
14661     char buf[MSG_SIZ];
14662
14663     clock = time((time_t *)NULL);
14664     tm = localtime(&clock);
14665     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14666             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14667     return StrSave(buf);
14668 }
14669
14670
14671 char *
14672 PositionToFEN(move, overrideCastling)
14673      int move;
14674      char *overrideCastling;
14675 {
14676     int i, j, fromX, fromY, toX, toY;
14677     int whiteToPlay;
14678     char buf[128];
14679     char *p, *q;
14680     int emptycount;
14681     ChessSquare piece;
14682
14683     whiteToPlay = (gameMode == EditPosition) ?
14684       !blackPlaysFirst : (move % 2 == 0);
14685     p = buf;
14686
14687     /* Piece placement data */
14688     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14689         emptycount = 0;
14690         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14691             if (boards[move][i][j] == EmptySquare) {
14692                 emptycount++;
14693             } else { ChessSquare piece = boards[move][i][j];
14694                 if (emptycount > 0) {
14695                     if(emptycount<10) /* [HGM] can be >= 10 */
14696                         *p++ = '0' + emptycount;
14697                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14698                     emptycount = 0;
14699                 }
14700                 if(PieceToChar(piece) == '+') {
14701                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14702                     *p++ = '+';
14703                     piece = (ChessSquare)(DEMOTED piece);
14704                 }
14705                 *p++ = PieceToChar(piece);
14706                 if(p[-1] == '~') {
14707                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14708                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14709                     *p++ = '~';
14710                 }
14711             }
14712         }
14713         if (emptycount > 0) {
14714             if(emptycount<10) /* [HGM] can be >= 10 */
14715                 *p++ = '0' + emptycount;
14716             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14717             emptycount = 0;
14718         }
14719         *p++ = '/';
14720     }
14721     *(p - 1) = ' ';
14722
14723     /* [HGM] print Crazyhouse or Shogi holdings */
14724     if( gameInfo.holdingsWidth ) {
14725         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14726         q = p;
14727         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14728             piece = boards[move][i][BOARD_WIDTH-1];
14729             if( piece != EmptySquare )
14730               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14731                   *p++ = PieceToChar(piece);
14732         }
14733         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14734             piece = boards[move][BOARD_HEIGHT-i-1][0];
14735             if( piece != EmptySquare )
14736               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14737                   *p++ = PieceToChar(piece);
14738         }
14739
14740         if( q == p ) *p++ = '-';
14741         *p++ = ']';
14742         *p++ = ' ';
14743     }
14744
14745     /* Active color */
14746     *p++ = whiteToPlay ? 'w' : 'b';
14747     *p++ = ' ';
14748
14749   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14750     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14751   } else {
14752   if(nrCastlingRights) {
14753      q = p;
14754      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14755        /* [HGM] write directly from rights */
14756            if(boards[move][CASTLING][2] != NoRights &&
14757               boards[move][CASTLING][0] != NoRights   )
14758                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14759            if(boards[move][CASTLING][2] != NoRights &&
14760               boards[move][CASTLING][1] != NoRights   )
14761                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14762            if(boards[move][CASTLING][5] != NoRights &&
14763               boards[move][CASTLING][3] != NoRights   )
14764                 *p++ = boards[move][CASTLING][3] + AAA;
14765            if(boards[move][CASTLING][5] != NoRights &&
14766               boards[move][CASTLING][4] != NoRights   )
14767                 *p++ = boards[move][CASTLING][4] + AAA;
14768      } else {
14769
14770         /* [HGM] write true castling rights */
14771         if( nrCastlingRights == 6 ) {
14772             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14773                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14774             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14775                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14776             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14777                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14778             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14779                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14780         }
14781      }
14782      if (q == p) *p++ = '-'; /* No castling rights */
14783      *p++ = ' ';
14784   }
14785
14786   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14787      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14788     /* En passant target square */
14789     if (move > backwardMostMove) {
14790         fromX = moveList[move - 1][0] - AAA;
14791         fromY = moveList[move - 1][1] - ONE;
14792         toX = moveList[move - 1][2] - AAA;
14793         toY = moveList[move - 1][3] - ONE;
14794         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14795             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14796             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14797             fromX == toX) {
14798             /* 2-square pawn move just happened */
14799             *p++ = toX + AAA;
14800             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14801         } else {
14802             *p++ = '-';
14803         }
14804     } else if(move == backwardMostMove) {
14805         // [HGM] perhaps we should always do it like this, and forget the above?
14806         if((signed char)boards[move][EP_STATUS] >= 0) {
14807             *p++ = boards[move][EP_STATUS] + AAA;
14808             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14809         } else {
14810             *p++ = '-';
14811         }
14812     } else {
14813         *p++ = '-';
14814     }
14815     *p++ = ' ';
14816   }
14817   }
14818
14819     /* [HGM] find reversible plies */
14820     {   int i = 0, j=move;
14821
14822         if (appData.debugMode) { int k;
14823             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14824             for(k=backwardMostMove; k<=forwardMostMove; k++)
14825                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14826
14827         }
14828
14829         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14830         if( j == backwardMostMove ) i += initialRulePlies;
14831         sprintf(p, "%d ", i);
14832         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14833     }
14834     /* Fullmove number */
14835     sprintf(p, "%d", (move / 2) + 1);
14836
14837     return StrSave(buf);
14838 }
14839
14840 Boolean
14841 ParseFEN(board, blackPlaysFirst, fen)
14842     Board board;
14843      int *blackPlaysFirst;
14844      char *fen;
14845 {
14846     int i, j;
14847     char *p, c;
14848     int emptycount;
14849     ChessSquare piece;
14850
14851     p = fen;
14852
14853     /* [HGM] by default clear Crazyhouse holdings, if present */
14854     if(gameInfo.holdingsWidth) {
14855        for(i=0; i<BOARD_HEIGHT; i++) {
14856            board[i][0]             = EmptySquare; /* black holdings */
14857            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14858            board[i][1]             = (ChessSquare) 0; /* black counts */
14859            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14860        }
14861     }
14862
14863     /* Piece placement data */
14864     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14865         j = 0;
14866         for (;;) {
14867             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14868                 if (*p == '/') p++;
14869                 emptycount = gameInfo.boardWidth - j;
14870                 while (emptycount--)
14871                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14872                 break;
14873 #if(BOARD_FILES >= 10)
14874             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14875                 p++; emptycount=10;
14876                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14877                 while (emptycount--)
14878                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14879 #endif
14880             } else if (isdigit(*p)) {
14881                 emptycount = *p++ - '0';
14882                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14883                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14884                 while (emptycount--)
14885                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14886             } else if (*p == '+' || isalpha(*p)) {
14887                 if (j >= gameInfo.boardWidth) return FALSE;
14888                 if(*p=='+') {
14889                     piece = CharToPiece(*++p);
14890                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14891                     piece = (ChessSquare) (PROMOTED piece ); p++;
14892                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14893                 } else piece = CharToPiece(*p++);
14894
14895                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14896                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14897                     piece = (ChessSquare) (PROMOTED piece);
14898                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14899                     p++;
14900                 }
14901                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14902             } else {
14903                 return FALSE;
14904             }
14905         }
14906     }
14907     while (*p == '/' || *p == ' ') p++;
14908
14909     /* [HGM] look for Crazyhouse holdings here */
14910     while(*p==' ') p++;
14911     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14912         if(*p == '[') p++;
14913         if(*p == '-' ) p++; /* empty holdings */ else {
14914             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14915             /* if we would allow FEN reading to set board size, we would   */
14916             /* have to add holdings and shift the board read so far here   */
14917             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14918                 p++;
14919                 if((int) piece >= (int) BlackPawn ) {
14920                     i = (int)piece - (int)BlackPawn;
14921                     i = PieceToNumber((ChessSquare)i);
14922                     if( i >= gameInfo.holdingsSize ) return FALSE;
14923                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14924                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14925                 } else {
14926                     i = (int)piece - (int)WhitePawn;
14927                     i = PieceToNumber((ChessSquare)i);
14928                     if( i >= gameInfo.holdingsSize ) return FALSE;
14929                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14930                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14931                 }
14932             }
14933         }
14934         if(*p == ']') p++;
14935     }
14936
14937     while(*p == ' ') p++;
14938
14939     /* Active color */
14940     c = *p++;
14941     if(appData.colorNickNames) {
14942       if( c == appData.colorNickNames[0] ) c = 'w'; else
14943       if( c == appData.colorNickNames[1] ) c = 'b';
14944     }
14945     switch (c) {
14946       case 'w':
14947         *blackPlaysFirst = FALSE;
14948         break;
14949       case 'b':
14950         *blackPlaysFirst = TRUE;
14951         break;
14952       default:
14953         return FALSE;
14954     }
14955
14956     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14957     /* return the extra info in global variiables             */
14958
14959     /* set defaults in case FEN is incomplete */
14960     board[EP_STATUS] = EP_UNKNOWN;
14961     for(i=0; i<nrCastlingRights; i++ ) {
14962         board[CASTLING][i] =
14963             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14964     }   /* assume possible unless obviously impossible */
14965     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14966     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14967     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14968                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14969     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14970     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14971     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14972                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14973     FENrulePlies = 0;
14974
14975     while(*p==' ') p++;
14976     if(nrCastlingRights) {
14977       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14978           /* castling indicator present, so default becomes no castlings */
14979           for(i=0; i<nrCastlingRights; i++ ) {
14980                  board[CASTLING][i] = NoRights;
14981           }
14982       }
14983       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14984              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14985              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14986              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14987         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14988
14989         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14990             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14991             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14992         }
14993         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14994             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14995         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14996                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14997         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14998                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14999         switch(c) {
15000           case'K':
15001               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15002               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15003               board[CASTLING][2] = whiteKingFile;
15004               break;
15005           case'Q':
15006               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15007               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15008               board[CASTLING][2] = whiteKingFile;
15009               break;
15010           case'k':
15011               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15012               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15013               board[CASTLING][5] = blackKingFile;
15014               break;
15015           case'q':
15016               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15017               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15018               board[CASTLING][5] = blackKingFile;
15019           case '-':
15020               break;
15021           default: /* FRC castlings */
15022               if(c >= 'a') { /* black rights */
15023                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15024                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15025                   if(i == BOARD_RGHT) break;
15026                   board[CASTLING][5] = i;
15027                   c -= AAA;
15028                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15029                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15030                   if(c > i)
15031                       board[CASTLING][3] = c;
15032                   else
15033                       board[CASTLING][4] = c;
15034               } else { /* white rights */
15035                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15036                     if(board[0][i] == WhiteKing) break;
15037                   if(i == BOARD_RGHT) break;
15038                   board[CASTLING][2] = i;
15039                   c -= AAA - 'a' + 'A';
15040                   if(board[0][c] >= WhiteKing) break;
15041                   if(c > i)
15042                       board[CASTLING][0] = c;
15043                   else
15044                       board[CASTLING][1] = c;
15045               }
15046         }
15047       }
15048       for(i=0; i<nrCastlingRights; i++)
15049         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15050     if (appData.debugMode) {
15051         fprintf(debugFP, "FEN castling rights:");
15052         for(i=0; i<nrCastlingRights; i++)
15053         fprintf(debugFP, " %d", board[CASTLING][i]);
15054         fprintf(debugFP, "\n");
15055     }
15056
15057       while(*p==' ') p++;
15058     }
15059
15060     /* read e.p. field in games that know e.p. capture */
15061     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15062        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15063       if(*p=='-') {
15064         p++; board[EP_STATUS] = EP_NONE;
15065       } else {
15066          char c = *p++ - AAA;
15067
15068          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15069          if(*p >= '0' && *p <='9') p++;
15070          board[EP_STATUS] = c;
15071       }
15072     }
15073
15074
15075     if(sscanf(p, "%d", &i) == 1) {
15076         FENrulePlies = i; /* 50-move ply counter */
15077         /* (The move number is still ignored)    */
15078     }
15079
15080     return TRUE;
15081 }
15082
15083 void
15084 EditPositionPasteFEN(char *fen)
15085 {
15086   if (fen != NULL) {
15087     Board initial_position;
15088
15089     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15090       DisplayError(_("Bad FEN position in clipboard"), 0);
15091       return ;
15092     } else {
15093       int savedBlackPlaysFirst = blackPlaysFirst;
15094       EditPositionEvent();
15095       blackPlaysFirst = savedBlackPlaysFirst;
15096       CopyBoard(boards[0], initial_position);
15097       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15098       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15099       DisplayBothClocks();
15100       DrawPosition(FALSE, boards[currentMove]);
15101     }
15102   }
15103 }
15104
15105 static char cseq[12] = "\\   ";
15106
15107 Boolean set_cont_sequence(char *new_seq)
15108 {
15109     int len;
15110     Boolean ret;
15111
15112     // handle bad attempts to set the sequence
15113         if (!new_seq)
15114                 return 0; // acceptable error - no debug
15115
15116     len = strlen(new_seq);
15117     ret = (len > 0) && (len < sizeof(cseq));
15118     if (ret)
15119       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15120     else if (appData.debugMode)
15121       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15122     return ret;
15123 }
15124
15125 /*
15126     reformat a source message so words don't cross the width boundary.  internal
15127     newlines are not removed.  returns the wrapped size (no null character unless
15128     included in source message).  If dest is NULL, only calculate the size required
15129     for the dest buffer.  lp argument indicats line position upon entry, and it's
15130     passed back upon exit.
15131 */
15132 int wrap(char *dest, char *src, int count, int width, int *lp)
15133 {
15134     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15135
15136     cseq_len = strlen(cseq);
15137     old_line = line = *lp;
15138     ansi = len = clen = 0;
15139
15140     for (i=0; i < count; i++)
15141     {
15142         if (src[i] == '\033')
15143             ansi = 1;
15144
15145         // if we hit the width, back up
15146         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15147         {
15148             // store i & len in case the word is too long
15149             old_i = i, old_len = len;
15150
15151             // find the end of the last word
15152             while (i && src[i] != ' ' && src[i] != '\n')
15153             {
15154                 i--;
15155                 len--;
15156             }
15157
15158             // word too long?  restore i & len before splitting it
15159             if ((old_i-i+clen) >= width)
15160             {
15161                 i = old_i;
15162                 len = old_len;
15163             }
15164
15165             // extra space?
15166             if (i && src[i-1] == ' ')
15167                 len--;
15168
15169             if (src[i] != ' ' && src[i] != '\n')
15170             {
15171                 i--;
15172                 if (len)
15173                     len--;
15174             }
15175
15176             // now append the newline and continuation sequence
15177             if (dest)
15178                 dest[len] = '\n';
15179             len++;
15180             if (dest)
15181                 strncpy(dest+len, cseq, cseq_len);
15182             len += cseq_len;
15183             line = cseq_len;
15184             clen = cseq_len;
15185             continue;
15186         }
15187
15188         if (dest)
15189             dest[len] = src[i];
15190         len++;
15191         if (!ansi)
15192             line++;
15193         if (src[i] == '\n')
15194             line = 0;
15195         if (src[i] == 'm')
15196             ansi = 0;
15197     }
15198     if (dest && appData.debugMode)
15199     {
15200         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15201             count, width, line, len, *lp);
15202         show_bytes(debugFP, src, count);
15203         fprintf(debugFP, "\ndest: ");
15204         show_bytes(debugFP, dest, len);
15205         fprintf(debugFP, "\n");
15206     }
15207     *lp = dest ? line : old_line;
15208
15209     return len;
15210 }
15211
15212 // [HGM] vari: routines for shelving variations
15213
15214 void
15215 PushTail(int firstMove, int lastMove)
15216 {
15217         int i, j, nrMoves = lastMove - firstMove;
15218
15219         if(appData.icsActive) { // only in local mode
15220                 forwardMostMove = currentMove; // mimic old ICS behavior
15221                 return;
15222         }
15223         if(storedGames >= MAX_VARIATIONS-1) return;
15224
15225         // push current tail of game on stack
15226         savedResult[storedGames] = gameInfo.result;
15227         savedDetails[storedGames] = gameInfo.resultDetails;
15228         gameInfo.resultDetails = NULL;
15229         savedFirst[storedGames] = firstMove;
15230         savedLast [storedGames] = lastMove;
15231         savedFramePtr[storedGames] = framePtr;
15232         framePtr -= nrMoves; // reserve space for the boards
15233         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15234             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15235             for(j=0; j<MOVE_LEN; j++)
15236                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15237             for(j=0; j<2*MOVE_LEN; j++)
15238                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15239             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15240             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15241             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15242             pvInfoList[firstMove+i-1].depth = 0;
15243             commentList[framePtr+i] = commentList[firstMove+i];
15244             commentList[firstMove+i] = NULL;
15245         }
15246
15247         storedGames++;
15248         forwardMostMove = firstMove; // truncate game so we can start variation
15249         if(storedGames == 1) GreyRevert(FALSE);
15250 }
15251
15252 Boolean
15253 PopTail(Boolean annotate)
15254 {
15255         int i, j, nrMoves;
15256         char buf[8000], moveBuf[20];
15257
15258         if(appData.icsActive) return FALSE; // only in local mode
15259         if(!storedGames) return FALSE; // sanity
15260         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15261
15262         storedGames--;
15263         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15264         nrMoves = savedLast[storedGames] - currentMove;
15265         if(annotate) {
15266                 int cnt = 10;
15267                 if(!WhiteOnMove(currentMove))
15268                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15269                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15270                 for(i=currentMove; i<forwardMostMove; i++) {
15271                         if(WhiteOnMove(i))
15272                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15273                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15274                         strcat(buf, moveBuf);
15275                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15276                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15277                 }
15278                 strcat(buf, ")");
15279         }
15280         for(i=1; i<=nrMoves; i++) { // copy last variation back
15281             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15282             for(j=0; j<MOVE_LEN; j++)
15283                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15284             for(j=0; j<2*MOVE_LEN; j++)
15285                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15286             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15287             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15288             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15289             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15290             commentList[currentMove+i] = commentList[framePtr+i];
15291             commentList[framePtr+i] = NULL;
15292         }
15293         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15294         framePtr = savedFramePtr[storedGames];
15295         gameInfo.result = savedResult[storedGames];
15296         if(gameInfo.resultDetails != NULL) {
15297             free(gameInfo.resultDetails);
15298       }
15299         gameInfo.resultDetails = savedDetails[storedGames];
15300         forwardMostMove = currentMove + nrMoves;
15301         if(storedGames == 0) GreyRevert(TRUE);
15302         return TRUE;
15303 }
15304
15305 void
15306 CleanupTail()
15307 {       // remove all shelved variations
15308         int i;
15309         for(i=0; i<storedGames; i++) {
15310             if(savedDetails[i])
15311                 free(savedDetails[i]);
15312             savedDetails[i] = NULL;
15313         }
15314         for(i=framePtr; i<MAX_MOVES; i++) {
15315                 if(commentList[i]) free(commentList[i]);
15316                 commentList[i] = NULL;
15317         }
15318         framePtr = MAX_MOVES-1;
15319         storedGames = 0;
15320 }
15321
15322 void
15323 LoadVariation(int index, char *text)
15324 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15325         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15326         int level = 0, move;
15327
15328         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15329         // first find outermost bracketing variation
15330         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15331             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15332                 if(*p == '{') wait = '}'; else
15333                 if(*p == '[') wait = ']'; else
15334                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15335                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15336             }
15337             if(*p == wait) wait = NULLCHAR; // closing ]} found
15338             p++;
15339         }
15340         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15341         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15342         end[1] = NULLCHAR; // clip off comment beyond variation
15343         ToNrEvent(currentMove-1);
15344         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15345         // kludge: use ParsePV() to append variation to game
15346         move = currentMove;
15347         ParsePV(start, TRUE);
15348         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15349         ClearPremoveHighlights();
15350         CommentPopDown();
15351         ToNrEvent(currentMove+1);
15352 }
15353