Bugfix for safeStrCpy patch for XBoard
[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 int shiftKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int sending_ICS_login    = 0;
456 int sending_ICS_password = 0;
457
458 int movesPerSession;
459 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
460 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
461 long timeControl_2; /* [AS] Allow separate time controls */
462 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
463 long timeRemaining[2][MAX_MOVES];
464 int matchGame = 0;
465 TimeMark programStartTime;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 int shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
530         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
531 };
532
533 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
535         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
537         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 };
539
540 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
542         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackMan, BlackFerz,
544         BlackKing, BlackMan, BlackKnight, BlackRook }
545 };
546
547
548 #if (BOARD_FILES>=10)
549 ChessSquare ShogiArray[2][BOARD_FILES] = {
550     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
551         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
552     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
553         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
554 };
555
556 ChessSquare XiangqiArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
558         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
560         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
561 };
562
563 ChessSquare CapablancaArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
567         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
568 };
569
570 ChessSquare GreatArray[2][BOARD_FILES] = {
571     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
572         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
573     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
574         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
575 };
576
577 ChessSquare JanusArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
579         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
580     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
581         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
582 };
583
584 #ifdef GOTHIC
585 ChessSquare GothicArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
587         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
589         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
590 };
591 #else // !GOTHIC
592 #define GothicArray CapablancaArray
593 #endif // !GOTHIC
594
595 #ifdef FALCON
596 ChessSquare FalconArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
598         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
600         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
601 };
602 #else // !FALCON
603 #define FalconArray CapablancaArray
604 #endif // !FALCON
605
606 #else // !(BOARD_FILES>=10)
607 #define XiangqiPosition FIDEArray
608 #define CapablancaArray FIDEArray
609 #define GothicArray FIDEArray
610 #define GreatArray FIDEArray
611 #endif // !(BOARD_FILES>=10)
612
613 #if (BOARD_FILES>=12)
614 ChessSquare CourierArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
616         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
618         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
619 };
620 #else // !(BOARD_FILES>=12)
621 #define CourierArray CapablancaArray
622 #endif // !(BOARD_FILES>=12)
623
624
625 Board initialPosition;
626
627
628 /* Convert str to a rating. Checks for special cases of "----",
629
630    "++++", etc. Also strips ()'s */
631 int
632 string_to_rating(str)
633   char *str;
634 {
635   while(*str && !isdigit(*str)) ++str;
636   if (!*str)
637     return 0;   /* One of the special "no rating" cases */
638   else
639     return atoi(str);
640 }
641
642 void
643 ClearProgramStats()
644 {
645     /* Init programStats */
646     programStats.movelist[0] = 0;
647     programStats.depth = 0;
648     programStats.nr_moves = 0;
649     programStats.moves_left = 0;
650     programStats.nodes = 0;
651     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
652     programStats.score = 0;
653     programStats.got_only_move = 0;
654     programStats.got_fail = 0;
655     programStats.line_is_book = 0;
656 }
657
658 void
659 InitBackEnd1()
660 {
661     int matched, min, sec;
662
663     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
664     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
665
666     GetTimeMark(&programStartTime);
667     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
668
669     ClearProgramStats();
670     programStats.ok_to_send = 1;
671     programStats.seen_stat = 0;
672
673     /*
674      * Initialize game list
675      */
676     ListNew(&gameList);
677
678
679     /*
680      * Internet chess server status
681      */
682     if (appData.icsActive) {
683         appData.matchMode = FALSE;
684         appData.matchGames = 0;
685 #if ZIPPY
686         appData.noChessProgram = !appData.zippyPlay;
687 #else
688         appData.zippyPlay = FALSE;
689         appData.zippyTalk = FALSE;
690         appData.noChessProgram = TRUE;
691 #endif
692         if (*appData.icsHelper != NULLCHAR) {
693             appData.useTelnet = TRUE;
694             appData.telnetProgram = appData.icsHelper;
695         }
696     } else {
697         appData.zippyTalk = appData.zippyPlay = FALSE;
698     }
699
700     /* [AS] Initialize pv info list [HGM] and game state */
701     {
702         int i, j;
703
704         for( i=0; i<=framePtr; i++ ) {
705             pvInfoList[i].depth = -1;
706             boards[i][EP_STATUS] = EP_NONE;
707             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
708         }
709     }
710
711     /*
712      * Parse timeControl resource
713      */
714     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
715                           appData.movesPerSession)) {
716         char buf[MSG_SIZ];
717         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
718         DisplayFatalError(buf, 0, 2);
719     }
720
721     /*
722      * Parse searchTime resource
723      */
724     if (*appData.searchTime != NULLCHAR) {
725         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
726         if (matched == 1) {
727             searchTime = min * 60;
728         } else if (matched == 2) {
729             searchTime = min * 60 + sec;
730         } else {
731             char buf[MSG_SIZ];
732             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
733             DisplayFatalError(buf, 0, 2);
734         }
735     }
736
737     /* [AS] Adjudication threshold */
738     adjudicateLossThreshold = appData.adjudicateLossThreshold;
739
740     first.which = _("first");
741     second.which = _("second");
742     first.maybeThinking = second.maybeThinking = FALSE;
743     first.pr = second.pr = NoProc;
744     first.isr = second.isr = NULL;
745     first.sendTime = second.sendTime = 2;
746     first.sendDrawOffers = 1;
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754     first.program = appData.firstChessProgram;
755     second.program = appData.secondChessProgram;
756     first.host = appData.firstHost;
757     second.host = appData.secondHost;
758     first.dir = appData.firstDirectory;
759     second.dir = appData.secondDirectory;
760     first.other = &second;
761     second.other = &first;
762     first.initString = appData.initString;
763     second.initString = appData.secondInitString;
764     first.computerString = appData.firstComputerString;
765     second.computerString = appData.secondComputerString;
766     first.useSigint = second.useSigint = TRUE;
767     first.useSigterm = second.useSigterm = TRUE;
768     first.reuse = appData.reuseFirst;
769     second.reuse = appData.reuseSecond;
770     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
771     second.nps = appData.secondNPS;
772     first.useSetboard = second.useSetboard = FALSE;
773     first.useSAN = second.useSAN = FALSE;
774     first.usePing = second.usePing = FALSE;
775     first.lastPing = second.lastPing = 0;
776     first.lastPong = second.lastPong = 0;
777     first.usePlayother = second.usePlayother = FALSE;
778     first.useColors = second.useColors = TRUE;
779     first.useUsermove = second.useUsermove = FALSE;
780     first.sendICS = second.sendICS = FALSE;
781     first.sendName = second.sendName = appData.icsActive;
782     first.sdKludge = second.sdKludge = FALSE;
783     first.stKludge = second.stKludge = FALSE;
784     TidyProgramName(first.program, first.host, first.tidy);
785     TidyProgramName(second.program, second.host, second.tidy);
786     first.matchWins = second.matchWins = 0;
787     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
788     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
789     first.analysisSupport = second.analysisSupport = 2; /* detect */
790     first.analyzing = second.analyzing = FALSE;
791     first.initDone = second.initDone = FALSE;
792
793     /* New features added by Tord: */
794     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
795     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
796     /* End of new features added by Tord. */
797     first.fenOverride  = appData.fenOverride1;
798     second.fenOverride = appData.fenOverride2;
799
800     /* [HGM] time odds: set factor for each machine */
801     first.timeOdds  = appData.firstTimeOdds;
802     second.timeOdds = appData.secondTimeOdds;
803     { float norm = 1;
804         if(appData.timeOddsMode) {
805             norm = first.timeOdds;
806             if(norm > second.timeOdds) norm = second.timeOdds;
807         }
808         first.timeOdds /= norm;
809         second.timeOdds /= norm;
810     }
811
812     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
813     first.accumulateTC = appData.firstAccumulateTC;
814     second.accumulateTC = appData.secondAccumulateTC;
815     first.maxNrOfSessions = second.maxNrOfSessions = 1;
816
817     /* [HGM] debug */
818     first.debug = second.debug = FALSE;
819     first.supportsNPS = second.supportsNPS = UNKNOWN;
820
821     /* [HGM] options */
822     first.optionSettings  = appData.firstOptions;
823     second.optionSettings = appData.secondOptions;
824
825     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
826     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
827     first.isUCI = appData.firstIsUCI; /* [AS] */
828     second.isUCI = appData.secondIsUCI; /* [AS] */
829     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
830     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
831
832     if (appData.firstProtocolVersion > PROTOVER
833         || appData.firstProtocolVersion < 1)
834       {
835         char buf[MSG_SIZ];
836         int len;
837
838         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
839                        appData.firstProtocolVersion);
840         if( (len > MSG_SIZ) && appData.debugMode )
841           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842
843         DisplayFatalError(buf, 0, 2);
844       }
845     else
846       {
847         first.protocolVersion = appData.firstProtocolVersion;
848       }
849
850     if (appData.secondProtocolVersion > PROTOVER
851         || appData.secondProtocolVersion < 1)
852       {
853         char buf[MSG_SIZ];
854         int len;
855
856         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
857                        appData.secondProtocolVersion);
858         if( (len > MSG_SIZ) && appData.debugMode )
859           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
860
861         DisplayFatalError(buf, 0, 2);
862       }
863     else
864       {
865         second.protocolVersion = appData.secondProtocolVersion;
866       }
867
868     if (appData.icsActive) {
869         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
870 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
871     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
872         appData.clockMode = FALSE;
873         first.sendTime = second.sendTime = 0;
874     }
875
876 #if ZIPPY
877     /* Override some settings from environment variables, for backward
878        compatibility.  Unfortunately it's not feasible to have the env
879        vars just set defaults, at least in xboard.  Ugh.
880     */
881     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
882       ZippyInit();
883     }
884 #endif
885
886     if (appData.noChessProgram) {
887         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
888         sprintf(programVersion, "%s", PACKAGE_STRING);
889     } else {
890       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
891       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
892       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
893     }
894
895     if (!appData.icsActive) {
896       char buf[MSG_SIZ];
897       int len;
898
899       /* Check for variants that are supported only in ICS mode,
900          or not at all.  Some that are accepted here nevertheless
901          have bugs; see comments below.
902       */
903       VariantClass variant = StringToVariant(appData.variant);
904       switch (variant) {
905       case VariantBughouse:     /* need four players and two boards */
906       case VariantKriegspiel:   /* need to hide pieces and move details */
907         /* case VariantFischeRandom: (Fabien: moved below) */
908         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
909         if( (len > MSG_SIZ) && appData.debugMode )
910           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
911
912         DisplayFatalError(buf, 0, 2);
913         return;
914
915       case VariantUnknown:
916       case VariantLoadable:
917       case Variant29:
918       case Variant30:
919       case Variant31:
920       case Variant32:
921       case Variant33:
922       case Variant34:
923       case Variant35:
924       case Variant36:
925       default:
926         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
927         if( (len > MSG_SIZ) && appData.debugMode )
928           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
929
930         DisplayFatalError(buf, 0, 2);
931         return;
932
933       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
934       case VariantFairy:      /* [HGM] TestLegality definitely off! */
935       case VariantGothic:     /* [HGM] should work */
936       case VariantCapablanca: /* [HGM] should work */
937       case VariantCourier:    /* [HGM] initial forced moves not implemented */
938       case VariantShogi:      /* [HGM] could still mate with pawn drop */
939       case VariantKnightmate: /* [HGM] should work */
940       case VariantCylinder:   /* [HGM] untested */
941       case VariantFalcon:     /* [HGM] untested */
942       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
943                                  offboard interposition not understood */
944       case VariantNormal:     /* definitely works! */
945       case VariantWildCastle: /* pieces not automatically shuffled */
946       case VariantNoCastle:   /* pieces not automatically shuffled */
947       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
948       case VariantLosers:     /* should work except for win condition,
949                                  and doesn't know captures are mandatory */
950       case VariantSuicide:    /* should work except for win condition,
951                                  and doesn't know captures are mandatory */
952       case VariantGiveaway:   /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantTwoKings:   /* should work */
955       case VariantAtomic:     /* should work except for win condition */
956       case Variant3Check:     /* should work except for win condition */
957       case VariantShatranj:   /* should work except for all win conditions */
958       case VariantMakruk:     /* should work except for daw countdown */
959       case VariantBerolina:   /* might work if TestLegality is off */
960       case VariantCapaRandom: /* should work */
961       case VariantJanus:      /* should work */
962       case VariantSuper:      /* experimental */
963       case VariantGreat:      /* experimental, requires legality testing to be off */
964         break;
965       }
966     }
967
968     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
969     InitEngineUCI( installDir, &second );
970 }
971
972 int NextIntegerFromString( char ** str, long * value )
973 {
974     int result = -1;
975     char * s = *str;
976
977     while( *s == ' ' || *s == '\t' ) {
978         s++;
979     }
980
981     *value = 0;
982
983     if( *s >= '0' && *s <= '9' ) {
984         while( *s >= '0' && *s <= '9' ) {
985             *value = *value * 10 + (*s - '0');
986             s++;
987         }
988
989         result = 0;
990     }
991
992     *str = s;
993
994     return result;
995 }
996
997 int NextTimeControlFromString( char ** str, long * value )
998 {
999     long temp;
1000     int result = NextIntegerFromString( str, &temp );
1001
1002     if( result == 0 ) {
1003         *value = temp * 60; /* Minutes */
1004         if( **str == ':' ) {
1005             (*str)++;
1006             result = NextIntegerFromString( str, &temp );
1007             *value += temp; /* Seconds */
1008         }
1009     }
1010
1011     return result;
1012 }
1013
1014 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1015 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1016     int result = -1, type = 0; long temp, temp2;
1017
1018     if(**str != ':') return -1; // old params remain in force!
1019     (*str)++;
1020     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1021     if( NextIntegerFromString( str, &temp ) ) return -1;
1022     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1023
1024     if(**str != '/') {
1025         /* time only: incremental or sudden-death time control */
1026         if(**str == '+') { /* increment follows; read it */
1027             (*str)++;
1028             if(**str == '!') type = *(*str)++; // Bronstein TC
1029             if(result = NextIntegerFromString( str, &temp2)) return -1;
1030             *inc = temp2 * 1000;
1031             if(**str == '.') { // read fraction of increment
1032                 char *start = ++(*str);
1033                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1034                 temp2 *= 1000;
1035                 while(start++ < *str) temp2 /= 10;
1036                 *inc += temp2;
1037             }
1038         } else *inc = 0;
1039         *moves = 0; *tc = temp * 1000; *incType = type;
1040         return 0;
1041     }
1042
1043     (*str)++; /* classical time control */
1044     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1045
1046     if(result == 0) {
1047         *moves = temp;
1048         *tc    = temp2 * 1000;
1049         *inc   = 0;
1050         *incType = type;
1051     }
1052     return result;
1053 }
1054
1055 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1056 {   /* [HGM] get time to add from the multi-session time-control string */
1057     int incType, moves=1; /* kludge to force reading of first session */
1058     long time, increment;
1059     char *s = tcString;
1060
1061     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1062     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1063     do {
1064         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1065         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1066         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1067         if(movenr == -1) return time;    /* last move before new session     */
1068         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1069         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1070         if(!moves) return increment;     /* current session is incremental   */
1071         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1072     } while(movenr >= -1);               /* try again for next session       */
1073
1074     return 0; // no new time quota on this move
1075 }
1076
1077 int
1078 ParseTimeControl(tc, ti, mps)
1079      char *tc;
1080      float ti;
1081      int mps;
1082 {
1083   long tc1;
1084   long tc2;
1085   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1086   int min, sec=0;
1087
1088   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1089   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1090       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1091   if(ti > 0) {
1092
1093     if(mps)
1094       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1095     else
1096       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1097   } else {
1098     if(mps)
1099       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1100     else
1101       snprintf(buf, MSG_SIZ, ":%s", mytc);
1102   }
1103   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1104
1105   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1106     return FALSE;
1107   }
1108
1109   if( *tc == '/' ) {
1110     /* Parse second time control */
1111     tc++;
1112
1113     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1114       return FALSE;
1115     }
1116
1117     if( tc2 == 0 ) {
1118       return FALSE;
1119     }
1120
1121     timeControl_2 = tc2 * 1000;
1122   }
1123   else {
1124     timeControl_2 = 0;
1125   }
1126
1127   if( tc1 == 0 ) {
1128     return FALSE;
1129   }
1130
1131   timeControl = tc1 * 1000;
1132
1133   if (ti >= 0) {
1134     timeIncrement = ti * 1000;  /* convert to ms */
1135     movesPerSession = 0;
1136   } else {
1137     timeIncrement = 0;
1138     movesPerSession = mps;
1139   }
1140   return TRUE;
1141 }
1142
1143 void
1144 InitBackEnd2()
1145 {
1146     if (appData.debugMode) {
1147         fprintf(debugFP, "%s\n", programVersion);
1148     }
1149
1150     set_cont_sequence(appData.wrapContSeq);
1151     if (appData.matchGames > 0) {
1152         appData.matchMode = TRUE;
1153     } else if (appData.matchMode) {
1154         appData.matchGames = 1;
1155     }
1156     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1157         appData.matchGames = appData.sameColorGames;
1158     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1159         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1160         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1161     }
1162     Reset(TRUE, FALSE);
1163     if (appData.noChessProgram || first.protocolVersion == 1) {
1164       InitBackEnd3();
1165     } else {
1166       /* kludge: allow timeout for initial "feature" commands */
1167       FreezeUI();
1168       DisplayMessage("", _("Starting chess program"));
1169       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1170     }
1171 }
1172
1173 void
1174 InitBackEnd3 P((void))
1175 {
1176     GameMode initialMode;
1177     char buf[MSG_SIZ];
1178     int err, len;
1179
1180     InitChessProgram(&first, startedFromSetupPosition);
1181
1182     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1183         free(programVersion);
1184         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1185         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1186     }
1187
1188     if (appData.icsActive) {
1189 #ifdef WIN32
1190         /* [DM] Make a console window if needed [HGM] merged ifs */
1191         ConsoleCreate();
1192 #endif
1193         err = establish();
1194         if (err != 0)
1195           {
1196             if (*appData.icsCommPort != NULLCHAR)
1197               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1198                              appData.icsCommPort);
1199             else
1200               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1201                         appData.icsHost, appData.icsPort);
1202
1203             if( (len > MSG_SIZ) && appData.debugMode )
1204               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1205
1206             DisplayFatalError(buf, err, 1);
1207             return;
1208         }
1209         SetICSMode();
1210         telnetISR =
1211           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1212         fromUserISR =
1213           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1214         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1215             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1216     } else if (appData.noChessProgram) {
1217         SetNCPMode();
1218     } else {
1219         SetGNUMode();
1220     }
1221
1222     if (*appData.cmailGameName != NULLCHAR) {
1223         SetCmailMode();
1224         OpenLoopback(&cmailPR);
1225         cmailISR =
1226           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1227     }
1228
1229     ThawUI();
1230     DisplayMessage("", "");
1231     if (StrCaseCmp(appData.initialMode, "") == 0) {
1232       initialMode = BeginningOfGame;
1233     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1234       initialMode = TwoMachinesPlay;
1235     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1236       initialMode = AnalyzeFile;
1237     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1238       initialMode = AnalyzeMode;
1239     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1240       initialMode = MachinePlaysWhite;
1241     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1242       initialMode = MachinePlaysBlack;
1243     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1244       initialMode = EditGame;
1245     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1246       initialMode = EditPosition;
1247     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1248       initialMode = Training;
1249     } else {
1250       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1251       if( (len > MSG_SIZ) && appData.debugMode )
1252         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1253
1254       DisplayFatalError(buf, 0, 2);
1255       return;
1256     }
1257
1258     if (appData.matchMode) {
1259         /* Set up machine vs. machine match */
1260         if (appData.noChessProgram) {
1261             DisplayFatalError(_("Can't have a match with no chess programs"),
1262                               0, 2);
1263             return;
1264         }
1265         matchMode = TRUE;
1266         matchGame = 1;
1267         if (*appData.loadGameFile != NULLCHAR) {
1268             int index = appData.loadGameIndex; // [HGM] autoinc
1269             if(index<0) lastIndex = index = 1;
1270             if (!LoadGameFromFile(appData.loadGameFile,
1271                                   index,
1272                                   appData.loadGameFile, FALSE)) {
1273                 DisplayFatalError(_("Bad game file"), 0, 1);
1274                 return;
1275             }
1276         } else if (*appData.loadPositionFile != NULLCHAR) {
1277             int index = appData.loadPositionIndex; // [HGM] autoinc
1278             if(index<0) lastIndex = index = 1;
1279             if (!LoadPositionFromFile(appData.loadPositionFile,
1280                                       index,
1281                                       appData.loadPositionFile)) {
1282                 DisplayFatalError(_("Bad position file"), 0, 1);
1283                 return;
1284             }
1285         }
1286         TwoMachinesEvent();
1287     } else if (*appData.cmailGameName != NULLCHAR) {
1288         /* Set up cmail mode */
1289         ReloadCmailMsgEvent(TRUE);
1290     } else {
1291         /* Set up other modes */
1292         if (initialMode == AnalyzeFile) {
1293           if (*appData.loadGameFile == NULLCHAR) {
1294             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1295             return;
1296           }
1297         }
1298         if (*appData.loadGameFile != NULLCHAR) {
1299             (void) LoadGameFromFile(appData.loadGameFile,
1300                                     appData.loadGameIndex,
1301                                     appData.loadGameFile, TRUE);
1302         } else if (*appData.loadPositionFile != NULLCHAR) {
1303             (void) LoadPositionFromFile(appData.loadPositionFile,
1304                                         appData.loadPositionIndex,
1305                                         appData.loadPositionFile);
1306             /* [HGM] try to make self-starting even after FEN load */
1307             /* to allow automatic setup of fairy variants with wtm */
1308             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1309                 gameMode = BeginningOfGame;
1310                 setboardSpoiledMachineBlack = 1;
1311             }
1312             /* [HGM] loadPos: make that every new game uses the setup */
1313             /* from file as long as we do not switch variant          */
1314             if(!blackPlaysFirst) {
1315                 startedFromPositionFile = TRUE;
1316                 CopyBoard(filePosition, boards[0]);
1317             }
1318         }
1319         if (initialMode == AnalyzeMode) {
1320           if (appData.noChessProgram) {
1321             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1322             return;
1323           }
1324           if (appData.icsActive) {
1325             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1326             return;
1327           }
1328           AnalyzeModeEvent();
1329         } else if (initialMode == AnalyzeFile) {
1330           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1331           ShowThinkingEvent();
1332           AnalyzeFileEvent();
1333           AnalysisPeriodicEvent(1);
1334         } else if (initialMode == MachinePlaysWhite) {
1335           if (appData.noChessProgram) {
1336             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1337                               0, 2);
1338             return;
1339           }
1340           if (appData.icsActive) {
1341             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1342                               0, 2);
1343             return;
1344           }
1345           MachineWhiteEvent();
1346         } else if (initialMode == MachinePlaysBlack) {
1347           if (appData.noChessProgram) {
1348             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1349                               0, 2);
1350             return;
1351           }
1352           if (appData.icsActive) {
1353             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1354                               0, 2);
1355             return;
1356           }
1357           MachineBlackEvent();
1358         } else if (initialMode == TwoMachinesPlay) {
1359           if (appData.noChessProgram) {
1360             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1361                               0, 2);
1362             return;
1363           }
1364           if (appData.icsActive) {
1365             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1366                               0, 2);
1367             return;
1368           }
1369           TwoMachinesEvent();
1370         } else if (initialMode == EditGame) {
1371           EditGameEvent();
1372         } else if (initialMode == EditPosition) {
1373           EditPositionEvent();
1374         } else if (initialMode == Training) {
1375           if (*appData.loadGameFile == NULLCHAR) {
1376             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1377             return;
1378           }
1379           TrainingEvent();
1380         }
1381     }
1382 }
1383
1384 /*
1385  * Establish will establish a contact to a remote host.port.
1386  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1387  *  used to talk to the host.
1388  * Returns 0 if okay, error code if not.
1389  */
1390 int
1391 establish()
1392 {
1393     char buf[MSG_SIZ];
1394
1395     if (*appData.icsCommPort != NULLCHAR) {
1396         /* Talk to the host through a serial comm port */
1397         return OpenCommPort(appData.icsCommPort, &icsPR);
1398
1399     } else if (*appData.gateway != NULLCHAR) {
1400         if (*appData.remoteShell == NULLCHAR) {
1401             /* Use the rcmd protocol to run telnet program on a gateway host */
1402             snprintf(buf, sizeof(buf), "%s %s %s",
1403                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1404             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1405
1406         } else {
1407             /* Use the rsh program to run telnet program on a gateway host */
1408             if (*appData.remoteUser == NULLCHAR) {
1409                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1410                         appData.gateway, appData.telnetProgram,
1411                         appData.icsHost, appData.icsPort);
1412             } else {
1413                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1414                         appData.remoteShell, appData.gateway,
1415                         appData.remoteUser, appData.telnetProgram,
1416                         appData.icsHost, appData.icsPort);
1417             }
1418             return StartChildProcess(buf, "", &icsPR);
1419
1420         }
1421     } else if (appData.useTelnet) {
1422         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1423
1424     } else {
1425         /* TCP socket interface differs somewhat between
1426            Unix and NT; handle details in the front end.
1427            */
1428         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1429     }
1430 }
1431
1432 void EscapeExpand(char *p, char *q)
1433 {       // [HGM] initstring: routine to shape up string arguments
1434         while(*p++ = *q++) if(p[-1] == '\\')
1435             switch(*q++) {
1436                 case 'n': p[-1] = '\n'; break;
1437                 case 'r': p[-1] = '\r'; break;
1438                 case 't': p[-1] = '\t'; break;
1439                 case '\\': p[-1] = '\\'; break;
1440                 case 0: *p = 0; return;
1441                 default: p[-1] = q[-1]; break;
1442             }
1443 }
1444
1445 void
1446 show_bytes(fp, buf, count)
1447      FILE *fp;
1448      char *buf;
1449      int count;
1450 {
1451     while (count--) {
1452         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1453             fprintf(fp, "\\%03o", *buf & 0xff);
1454         } else {
1455             putc(*buf, fp);
1456         }
1457         buf++;
1458     }
1459     fflush(fp);
1460 }
1461
1462 /* Returns an errno value */
1463 int
1464 OutputMaybeTelnet(pr, message, count, outError)
1465      ProcRef pr;
1466      char *message;
1467      int count;
1468      int *outError;
1469 {
1470     char buf[8192], *p, *q, *buflim;
1471     int left, newcount, outcount;
1472
1473     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1474         *appData.gateway != NULLCHAR) {
1475         if (appData.debugMode) {
1476             fprintf(debugFP, ">ICS: ");
1477             show_bytes(debugFP, message, count);
1478             fprintf(debugFP, "\n");
1479         }
1480         return OutputToProcess(pr, message, count, outError);
1481     }
1482
1483     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1484     p = message;
1485     q = buf;
1486     left = count;
1487     newcount = 0;
1488     while (left) {
1489         if (q >= buflim) {
1490             if (appData.debugMode) {
1491                 fprintf(debugFP, ">ICS: ");
1492                 show_bytes(debugFP, buf, newcount);
1493                 fprintf(debugFP, "\n");
1494             }
1495             outcount = OutputToProcess(pr, buf, newcount, outError);
1496             if (outcount < newcount) return -1; /* to be sure */
1497             q = buf;
1498             newcount = 0;
1499         }
1500         if (*p == '\n') {
1501             *q++ = '\r';
1502             newcount++;
1503         } else if (((unsigned char) *p) == TN_IAC) {
1504             *q++ = (char) TN_IAC;
1505             newcount ++;
1506         }
1507         *q++ = *p++;
1508         newcount++;
1509         left--;
1510     }
1511     if (appData.debugMode) {
1512         fprintf(debugFP, ">ICS: ");
1513         show_bytes(debugFP, buf, newcount);
1514         fprintf(debugFP, "\n");
1515     }
1516     outcount = OutputToProcess(pr, buf, newcount, outError);
1517     if (outcount < newcount) return -1; /* to be sure */
1518     return count;
1519 }
1520
1521 void
1522 read_from_player(isr, closure, message, count, error)
1523      InputSourceRef isr;
1524      VOIDSTAR closure;
1525      char *message;
1526      int count;
1527      int error;
1528 {
1529     int outError, outCount;
1530     static int gotEof = 0;
1531
1532     /* Pass data read from player on to ICS */
1533     if (count > 0) {
1534         gotEof = 0;
1535         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1536         if (outCount < count) {
1537             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1538         }
1539     } else if (count < 0) {
1540         RemoveInputSource(isr);
1541         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1542     } else if (gotEof++ > 0) {
1543         RemoveInputSource(isr);
1544         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1545     }
1546 }
1547
1548 void
1549 KeepAlive()
1550 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1551     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1552     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1553     SendToICS("date\n");
1554     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1555 }
1556
1557 /* added routine for printf style output to ics */
1558 void ics_printf(char *format, ...)
1559 {
1560     char buffer[MSG_SIZ];
1561     va_list args;
1562
1563     va_start(args, format);
1564     vsnprintf(buffer, sizeof(buffer), format, args);
1565     buffer[sizeof(buffer)-1] = '\0';
1566     SendToICS(buffer);
1567     va_end(args);
1568 }
1569
1570 void
1571 SendToICS(s)
1572      char *s;
1573 {
1574     int count, outCount, outError;
1575
1576     if (icsPR == NULL) return;
1577
1578     count = strlen(s);
1579     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1580     if (outCount < count) {
1581         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1582     }
1583 }
1584
1585 /* This is used for sending logon scripts to the ICS. Sending
1586    without a delay causes problems when using timestamp on ICC
1587    (at least on my machine). */
1588 void
1589 SendToICSDelayed(s,msdelay)
1590      char *s;
1591      long msdelay;
1592 {
1593     int count, outCount, outError;
1594
1595     if (icsPR == NULL) return;
1596
1597     count = strlen(s);
1598     if (appData.debugMode) {
1599         fprintf(debugFP, ">ICS: ");
1600         show_bytes(debugFP, s, count);
1601         fprintf(debugFP, "\n");
1602     }
1603     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1604                                       msdelay);
1605     if (outCount < count) {
1606         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1607     }
1608 }
1609
1610
1611 /* Remove all highlighting escape sequences in s
1612    Also deletes any suffix starting with '('
1613    */
1614 char *
1615 StripHighlightAndTitle(s)
1616      char *s;
1617 {
1618     static char retbuf[MSG_SIZ];
1619     char *p = retbuf;
1620
1621     while (*s != NULLCHAR) {
1622         while (*s == '\033') {
1623             while (*s != NULLCHAR && !isalpha(*s)) s++;
1624             if (*s != NULLCHAR) s++;
1625         }
1626         while (*s != NULLCHAR && *s != '\033') {
1627             if (*s == '(' || *s == '[') {
1628                 *p = NULLCHAR;
1629                 return retbuf;
1630             }
1631             *p++ = *s++;
1632         }
1633     }
1634     *p = NULLCHAR;
1635     return retbuf;
1636 }
1637
1638 /* Remove all highlighting escape sequences in s */
1639 char *
1640 StripHighlight(s)
1641      char *s;
1642 {
1643     static char retbuf[MSG_SIZ];
1644     char *p = retbuf;
1645
1646     while (*s != NULLCHAR) {
1647         while (*s == '\033') {
1648             while (*s != NULLCHAR && !isalpha(*s)) s++;
1649             if (*s != NULLCHAR) s++;
1650         }
1651         while (*s != NULLCHAR && *s != '\033') {
1652             *p++ = *s++;
1653         }
1654     }
1655     *p = NULLCHAR;
1656     return retbuf;
1657 }
1658
1659 char *variantNames[] = VARIANT_NAMES;
1660 char *
1661 VariantName(v)
1662      VariantClass v;
1663 {
1664     return variantNames[v];
1665 }
1666
1667
1668 /* Identify a variant from the strings the chess servers use or the
1669    PGN Variant tag names we use. */
1670 VariantClass
1671 StringToVariant(e)
1672      char *e;
1673 {
1674     char *p;
1675     int wnum = -1;
1676     VariantClass v = VariantNormal;
1677     int i, found = FALSE;
1678     char buf[MSG_SIZ];
1679     int len;
1680
1681     if (!e) return v;
1682
1683     /* [HGM] skip over optional board-size prefixes */
1684     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1685         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1686         while( *e++ != '_');
1687     }
1688
1689     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1690         v = VariantNormal;
1691         found = TRUE;
1692     } else
1693     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1694       if (StrCaseStr(e, variantNames[i])) {
1695         v = (VariantClass) i;
1696         found = TRUE;
1697         break;
1698       }
1699     }
1700
1701     if (!found) {
1702       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1703           || StrCaseStr(e, "wild/fr")
1704           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1705         v = VariantFischeRandom;
1706       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1707                  (i = 1, p = StrCaseStr(e, "w"))) {
1708         p += i;
1709         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1710         if (isdigit(*p)) {
1711           wnum = atoi(p);
1712         } else {
1713           wnum = -1;
1714         }
1715         switch (wnum) {
1716         case 0: /* FICS only, actually */
1717         case 1:
1718           /* Castling legal even if K starts on d-file */
1719           v = VariantWildCastle;
1720           break;
1721         case 2:
1722         case 3:
1723         case 4:
1724           /* Castling illegal even if K & R happen to start in
1725              normal positions. */
1726           v = VariantNoCastle;
1727           break;
1728         case 5:
1729         case 7:
1730         case 8:
1731         case 10:
1732         case 11:
1733         case 12:
1734         case 13:
1735         case 14:
1736         case 15:
1737         case 18:
1738         case 19:
1739           /* Castling legal iff K & R start in normal positions */
1740           v = VariantNormal;
1741           break;
1742         case 6:
1743         case 20:
1744         case 21:
1745           /* Special wilds for position setup; unclear what to do here */
1746           v = VariantLoadable;
1747           break;
1748         case 9:
1749           /* Bizarre ICC game */
1750           v = VariantTwoKings;
1751           break;
1752         case 16:
1753           v = VariantKriegspiel;
1754           break;
1755         case 17:
1756           v = VariantLosers;
1757           break;
1758         case 22:
1759           v = VariantFischeRandom;
1760           break;
1761         case 23:
1762           v = VariantCrazyhouse;
1763           break;
1764         case 24:
1765           v = VariantBughouse;
1766           break;
1767         case 25:
1768           v = Variant3Check;
1769           break;
1770         case 26:
1771           /* Not quite the same as FICS suicide! */
1772           v = VariantGiveaway;
1773           break;
1774         case 27:
1775           v = VariantAtomic;
1776           break;
1777         case 28:
1778           v = VariantShatranj;
1779           break;
1780
1781         /* Temporary names for future ICC types.  The name *will* change in
1782            the next xboard/WinBoard release after ICC defines it. */
1783         case 29:
1784           v = Variant29;
1785           break;
1786         case 30:
1787           v = Variant30;
1788           break;
1789         case 31:
1790           v = Variant31;
1791           break;
1792         case 32:
1793           v = Variant32;
1794           break;
1795         case 33:
1796           v = Variant33;
1797           break;
1798         case 34:
1799           v = Variant34;
1800           break;
1801         case 35:
1802           v = Variant35;
1803           break;
1804         case 36:
1805           v = Variant36;
1806           break;
1807         case 37:
1808           v = VariantShogi;
1809           break;
1810         case 38:
1811           v = VariantXiangqi;
1812           break;
1813         case 39:
1814           v = VariantCourier;
1815           break;
1816         case 40:
1817           v = VariantGothic;
1818           break;
1819         case 41:
1820           v = VariantCapablanca;
1821           break;
1822         case 42:
1823           v = VariantKnightmate;
1824           break;
1825         case 43:
1826           v = VariantFairy;
1827           break;
1828         case 44:
1829           v = VariantCylinder;
1830           break;
1831         case 45:
1832           v = VariantFalcon;
1833           break;
1834         case 46:
1835           v = VariantCapaRandom;
1836           break;
1837         case 47:
1838           v = VariantBerolina;
1839           break;
1840         case 48:
1841           v = VariantJanus;
1842           break;
1843         case 49:
1844           v = VariantSuper;
1845           break;
1846         case 50:
1847           v = VariantGreat;
1848           break;
1849         case -1:
1850           /* Found "wild" or "w" in the string but no number;
1851              must assume it's normal chess. */
1852           v = VariantNormal;
1853           break;
1854         default:
1855           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1856           if( (len > MSG_SIZ) && appData.debugMode )
1857             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1858
1859           DisplayError(buf, 0);
1860           v = VariantUnknown;
1861           break;
1862         }
1863       }
1864     }
1865     if (appData.debugMode) {
1866       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1867               e, wnum, VariantName(v));
1868     }
1869     return v;
1870 }
1871
1872 static int leftover_start = 0, leftover_len = 0;
1873 char star_match[STAR_MATCH_N][MSG_SIZ];
1874
1875 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1876    advance *index beyond it, and set leftover_start to the new value of
1877    *index; else return FALSE.  If pattern contains the character '*', it
1878    matches any sequence of characters not containing '\r', '\n', or the
1879    character following the '*' (if any), and the matched sequence(s) are
1880    copied into star_match.
1881    */
1882 int
1883 looking_at(buf, index, pattern)
1884      char *buf;
1885      int *index;
1886      char *pattern;
1887 {
1888     char *bufp = &buf[*index], *patternp = pattern;
1889     int star_count = 0;
1890     char *matchp = star_match[0];
1891
1892     for (;;) {
1893         if (*patternp == NULLCHAR) {
1894             *index = leftover_start = bufp - buf;
1895             *matchp = NULLCHAR;
1896             return TRUE;
1897         }
1898         if (*bufp == NULLCHAR) return FALSE;
1899         if (*patternp == '*') {
1900             if (*bufp == *(patternp + 1)) {
1901                 *matchp = NULLCHAR;
1902                 matchp = star_match[++star_count];
1903                 patternp += 2;
1904                 bufp++;
1905                 continue;
1906             } else if (*bufp == '\n' || *bufp == '\r') {
1907                 patternp++;
1908                 if (*patternp == NULLCHAR)
1909                   continue;
1910                 else
1911                   return FALSE;
1912             } else {
1913                 *matchp++ = *bufp++;
1914                 continue;
1915             }
1916         }
1917         if (*patternp != *bufp) return FALSE;
1918         patternp++;
1919         bufp++;
1920     }
1921 }
1922
1923 void
1924 SendToPlayer(data, length)
1925      char *data;
1926      int length;
1927 {
1928     int error, outCount;
1929     outCount = OutputToProcess(NoProc, data, length, &error);
1930     if (outCount < length) {
1931         DisplayFatalError(_("Error writing to display"), error, 1);
1932     }
1933 }
1934
1935 void
1936 PackHolding(packed, holding)
1937      char packed[];
1938      char *holding;
1939 {
1940     char *p = holding;
1941     char *q = packed;
1942     int runlength = 0;
1943     int curr = 9999;
1944     do {
1945         if (*p == curr) {
1946             runlength++;
1947         } else {
1948             switch (runlength) {
1949               case 0:
1950                 break;
1951               case 1:
1952                 *q++ = curr;
1953                 break;
1954               case 2:
1955                 *q++ = curr;
1956                 *q++ = curr;
1957                 break;
1958               default:
1959                 sprintf(q, "%d", runlength);
1960                 while (*q) q++;
1961                 *q++ = curr;
1962                 break;
1963             }
1964             runlength = 1;
1965             curr = *p;
1966         }
1967     } while (*p++);
1968     *q = NULLCHAR;
1969 }
1970
1971 /* Telnet protocol requests from the front end */
1972 void
1973 TelnetRequest(ddww, option)
1974      unsigned char ddww, option;
1975 {
1976     unsigned char msg[3];
1977     int outCount, outError;
1978
1979     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1980
1981     if (appData.debugMode) {
1982         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1983         switch (ddww) {
1984           case TN_DO:
1985             ddwwStr = "DO";
1986             break;
1987           case TN_DONT:
1988             ddwwStr = "DONT";
1989             break;
1990           case TN_WILL:
1991             ddwwStr = "WILL";
1992             break;
1993           case TN_WONT:
1994             ddwwStr = "WONT";
1995             break;
1996           default:
1997             ddwwStr = buf1;
1998             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1999             break;
2000         }
2001         switch (option) {
2002           case TN_ECHO:
2003             optionStr = "ECHO";
2004             break;
2005           default:
2006             optionStr = buf2;
2007             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2008             break;
2009         }
2010         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2011     }
2012     msg[0] = TN_IAC;
2013     msg[1] = ddww;
2014     msg[2] = option;
2015     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2016     if (outCount < 3) {
2017         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2018     }
2019 }
2020
2021 void
2022 DoEcho()
2023 {
2024     if (!appData.icsActive) return;
2025     TelnetRequest(TN_DO, TN_ECHO);
2026 }
2027
2028 void
2029 DontEcho()
2030 {
2031     if (!appData.icsActive) return;
2032     TelnetRequest(TN_DONT, TN_ECHO);
2033 }
2034
2035 void
2036 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2037 {
2038     /* put the holdings sent to us by the server on the board holdings area */
2039     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2040     char p;
2041     ChessSquare piece;
2042
2043     if(gameInfo.holdingsWidth < 2)  return;
2044     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2045         return; // prevent overwriting by pre-board holdings
2046
2047     if( (int)lowestPiece >= BlackPawn ) {
2048         holdingsColumn = 0;
2049         countsColumn = 1;
2050         holdingsStartRow = BOARD_HEIGHT-1;
2051         direction = -1;
2052     } else {
2053         holdingsColumn = BOARD_WIDTH-1;
2054         countsColumn = BOARD_WIDTH-2;
2055         holdingsStartRow = 0;
2056         direction = 1;
2057     }
2058
2059     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2060         board[i][holdingsColumn] = EmptySquare;
2061         board[i][countsColumn]   = (ChessSquare) 0;
2062     }
2063     while( (p=*holdings++) != NULLCHAR ) {
2064         piece = CharToPiece( ToUpper(p) );
2065         if(piece == EmptySquare) continue;
2066         /*j = (int) piece - (int) WhitePawn;*/
2067         j = PieceToNumber(piece);
2068         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2069         if(j < 0) continue;               /* should not happen */
2070         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2071         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2072         board[holdingsStartRow+j*direction][countsColumn]++;
2073     }
2074 }
2075
2076
2077 void
2078 VariantSwitch(Board board, VariantClass newVariant)
2079 {
2080    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2081    static Board oldBoard;
2082
2083    startedFromPositionFile = FALSE;
2084    if(gameInfo.variant == newVariant) return;
2085
2086    /* [HGM] This routine is called each time an assignment is made to
2087     * gameInfo.variant during a game, to make sure the board sizes
2088     * are set to match the new variant. If that means adding or deleting
2089     * holdings, we shift the playing board accordingly
2090     * This kludge is needed because in ICS observe mode, we get boards
2091     * of an ongoing game without knowing the variant, and learn about the
2092     * latter only later. This can be because of the move list we requested,
2093     * in which case the game history is refilled from the beginning anyway,
2094     * but also when receiving holdings of a crazyhouse game. In the latter
2095     * case we want to add those holdings to the already received position.
2096     */
2097
2098
2099    if (appData.debugMode) {
2100      fprintf(debugFP, "Switch board from %s to %s\n",
2101              VariantName(gameInfo.variant), VariantName(newVariant));
2102      setbuf(debugFP, NULL);
2103    }
2104    shuffleOpenings = 0;       /* [HGM] shuffle */
2105    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2106    switch(newVariant)
2107      {
2108      case VariantShogi:
2109        newWidth = 9;  newHeight = 9;
2110        gameInfo.holdingsSize = 7;
2111      case VariantBughouse:
2112      case VariantCrazyhouse:
2113        newHoldingsWidth = 2; break;
2114      case VariantGreat:
2115        newWidth = 10;
2116      case VariantSuper:
2117        newHoldingsWidth = 2;
2118        gameInfo.holdingsSize = 8;
2119        break;
2120      case VariantGothic:
2121      case VariantCapablanca:
2122      case VariantCapaRandom:
2123        newWidth = 10;
2124      default:
2125        newHoldingsWidth = gameInfo.holdingsSize = 0;
2126      };
2127
2128    if(newWidth  != gameInfo.boardWidth  ||
2129       newHeight != gameInfo.boardHeight ||
2130       newHoldingsWidth != gameInfo.holdingsWidth ) {
2131
2132      /* shift position to new playing area, if needed */
2133      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2134        for(i=0; i<BOARD_HEIGHT; i++)
2135          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2136            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2137              board[i][j];
2138        for(i=0; i<newHeight; i++) {
2139          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2140          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2141        }
2142      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2143        for(i=0; i<BOARD_HEIGHT; i++)
2144          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2145            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2146              board[i][j];
2147      }
2148      gameInfo.boardWidth  = newWidth;
2149      gameInfo.boardHeight = newHeight;
2150      gameInfo.holdingsWidth = newHoldingsWidth;
2151      gameInfo.variant = newVariant;
2152      InitDrawingSizes(-2, 0);
2153    } else gameInfo.variant = newVariant;
2154    CopyBoard(oldBoard, board);   // remember correctly formatted board
2155      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2156    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2157 }
2158
2159 static int loggedOn = FALSE;
2160
2161 /*-- Game start info cache: --*/
2162 int gs_gamenum;
2163 char gs_kind[MSG_SIZ];
2164 static char player1Name[128] = "";
2165 static char player2Name[128] = "";
2166 static char cont_seq[] = "\n\\   ";
2167 static int player1Rating = -1;
2168 static int player2Rating = -1;
2169 /*----------------------------*/
2170
2171 ColorClass curColor = ColorNormal;
2172 int suppressKibitz = 0;
2173
2174 // [HGM] seekgraph
2175 Boolean soughtPending = FALSE;
2176 Boolean seekGraphUp;
2177 #define MAX_SEEK_ADS 200
2178 #define SQUARE 0x80
2179 char *seekAdList[MAX_SEEK_ADS];
2180 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2181 float tcList[MAX_SEEK_ADS];
2182 char colorList[MAX_SEEK_ADS];
2183 int nrOfSeekAds = 0;
2184 int minRating = 1010, maxRating = 2800;
2185 int hMargin = 10, vMargin = 20, h, w;
2186 extern int squareSize, lineGap;
2187
2188 void
2189 PlotSeekAd(int i)
2190 {
2191         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2192         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2193         if(r < minRating+100 && r >=0 ) r = minRating+100;
2194         if(r > maxRating) r = maxRating;
2195         if(tc < 1.) tc = 1.;
2196         if(tc > 95.) tc = 95.;
2197         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2198         y = ((double)r - minRating)/(maxRating - minRating)
2199             * (h-vMargin-squareSize/8-1) + vMargin;
2200         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2201         if(strstr(seekAdList[i], " u ")) color = 1;
2202         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2203            !strstr(seekAdList[i], "bullet") &&
2204            !strstr(seekAdList[i], "blitz") &&
2205            !strstr(seekAdList[i], "standard") ) color = 2;
2206         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2207         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2208 }
2209
2210 void
2211 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2212 {
2213         char buf[MSG_SIZ], *ext = "";
2214         VariantClass v = StringToVariant(type);
2215         if(strstr(type, "wild")) {
2216             ext = type + 4; // append wild number
2217             if(v == VariantFischeRandom) type = "chess960"; else
2218             if(v == VariantLoadable) type = "setup"; else
2219             type = VariantName(v);
2220         }
2221         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2222         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2223             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2224             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2225             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2226             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2227             seekNrList[nrOfSeekAds] = nr;
2228             zList[nrOfSeekAds] = 0;
2229             seekAdList[nrOfSeekAds++] = StrSave(buf);
2230             if(plot) PlotSeekAd(nrOfSeekAds-1);
2231         }
2232 }
2233
2234 void
2235 EraseSeekDot(int i)
2236 {
2237     int x = xList[i], y = yList[i], d=squareSize/4, k;
2238     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2239     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2240     // now replot every dot that overlapped
2241     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2242         int xx = xList[k], yy = yList[k];
2243         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2244             DrawSeekDot(xx, yy, colorList[k]);
2245     }
2246 }
2247
2248 void
2249 RemoveSeekAd(int nr)
2250 {
2251         int i;
2252         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2253             EraseSeekDot(i);
2254             if(seekAdList[i]) free(seekAdList[i]);
2255             seekAdList[i] = seekAdList[--nrOfSeekAds];
2256             seekNrList[i] = seekNrList[nrOfSeekAds];
2257             ratingList[i] = ratingList[nrOfSeekAds];
2258             colorList[i]  = colorList[nrOfSeekAds];
2259             tcList[i] = tcList[nrOfSeekAds];
2260             xList[i]  = xList[nrOfSeekAds];
2261             yList[i]  = yList[nrOfSeekAds];
2262             zList[i]  = zList[nrOfSeekAds];
2263             seekAdList[nrOfSeekAds] = NULL;
2264             break;
2265         }
2266 }
2267
2268 Boolean
2269 MatchSoughtLine(char *line)
2270 {
2271     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2272     int nr, base, inc, u=0; char dummy;
2273
2274     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2275        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2276        (u=1) &&
2277        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2278         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2279         // match: compact and save the line
2280         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2281         return TRUE;
2282     }
2283     return FALSE;
2284 }
2285
2286 int
2287 DrawSeekGraph()
2288 {
2289     int i;
2290     if(!seekGraphUp) return FALSE;
2291     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2292     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2293
2294     DrawSeekBackground(0, 0, w, h);
2295     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2296     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2297     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2298         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2299         yy = h-1-yy;
2300         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2301         if(i%500 == 0) {
2302             char buf[MSG_SIZ];
2303             snprintf(buf, MSG_SIZ, "%d", i);
2304             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2305         }
2306     }
2307     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2308     for(i=1; i<100; i+=(i<10?1:5)) {
2309         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2310         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2311         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2312             char buf[MSG_SIZ];
2313             snprintf(buf, MSG_SIZ, "%d", i);
2314             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2315         }
2316     }
2317     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2318     return TRUE;
2319 }
2320
2321 int SeekGraphClick(ClickType click, int x, int y, int moving)
2322 {
2323     static int lastDown = 0, displayed = 0, lastSecond;
2324     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2325         if(click == Release || moving) return FALSE;
2326         nrOfSeekAds = 0;
2327         soughtPending = TRUE;
2328         SendToICS(ics_prefix);
2329         SendToICS("sought\n"); // should this be "sought all"?
2330     } else { // issue challenge based on clicked ad
2331         int dist = 10000; int i, closest = 0, second = 0;
2332         for(i=0; i<nrOfSeekAds; i++) {
2333             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2334             if(d < dist) { dist = d; closest = i; }
2335             second += (d - zList[i] < 120); // count in-range ads
2336             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2337         }
2338         if(dist < 120) {
2339             char buf[MSG_SIZ];
2340             second = (second > 1);
2341             if(displayed != closest || second != lastSecond) {
2342                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2343                 lastSecond = second; displayed = closest;
2344             }
2345             if(click == Press) {
2346                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2347                 lastDown = closest;
2348                 return TRUE;
2349             } // on press 'hit', only show info
2350             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2351             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2352             SendToICS(ics_prefix);
2353             SendToICS(buf);
2354             return TRUE; // let incoming board of started game pop down the graph
2355         } else if(click == Release) { // release 'miss' is ignored
2356             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2357             if(moving == 2) { // right up-click
2358                 nrOfSeekAds = 0; // refresh graph
2359                 soughtPending = TRUE;
2360                 SendToICS(ics_prefix);
2361                 SendToICS("sought\n"); // should this be "sought all"?
2362             }
2363             return TRUE;
2364         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2365         // press miss or release hit 'pop down' seek graph
2366         seekGraphUp = FALSE;
2367         DrawPosition(TRUE, NULL);
2368     }
2369     return TRUE;
2370 }
2371
2372 void
2373 read_from_ics(isr, closure, data, count, error)
2374      InputSourceRef isr;
2375      VOIDSTAR closure;
2376      char *data;
2377      int count;
2378      int error;
2379 {
2380 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2381 #define STARTED_NONE 0
2382 #define STARTED_MOVES 1
2383 #define STARTED_BOARD 2
2384 #define STARTED_OBSERVE 3
2385 #define STARTED_HOLDINGS 4
2386 #define STARTED_CHATTER 5
2387 #define STARTED_COMMENT 6
2388 #define STARTED_MOVES_NOHIDE 7
2389
2390     static int started = STARTED_NONE;
2391     static char parse[20000];
2392     static int parse_pos = 0;
2393     static char buf[BUF_SIZE + 1];
2394     static int firstTime = TRUE, intfSet = FALSE;
2395     static ColorClass prevColor = ColorNormal;
2396     static int savingComment = FALSE;
2397     static int cmatch = 0; // continuation sequence match
2398     char *bp;
2399     char str[MSG_SIZ];
2400     int i, oldi;
2401     int buf_len;
2402     int next_out;
2403     int tkind;
2404     int backup;    /* [DM] For zippy color lines */
2405     char *p;
2406     char talker[MSG_SIZ]; // [HGM] chat
2407     int channel;
2408
2409     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2410
2411     if (appData.debugMode) {
2412       if (!error) {
2413         fprintf(debugFP, "<ICS: ");
2414         show_bytes(debugFP, data, count);
2415         fprintf(debugFP, "\n");
2416       }
2417     }
2418
2419     if (appData.debugMode) { int f = forwardMostMove;
2420         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2421                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2422                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2423     }
2424     if (count > 0) {
2425         /* If last read ended with a partial line that we couldn't parse,
2426            prepend it to the new read and try again. */
2427         if (leftover_len > 0) {
2428             for (i=0; i<leftover_len; i++)
2429               buf[i] = buf[leftover_start + i];
2430         }
2431
2432     /* copy new characters into the buffer */
2433     bp = buf + leftover_len;
2434     buf_len=leftover_len;
2435     for (i=0; i<count; i++)
2436     {
2437         // ignore these
2438         if (data[i] == '\r')
2439             continue;
2440
2441         // join lines split by ICS?
2442         if (!appData.noJoin)
2443         {
2444             /*
2445                 Joining just consists of finding matches against the
2446                 continuation sequence, and discarding that sequence
2447                 if found instead of copying it.  So, until a match
2448                 fails, there's nothing to do since it might be the
2449                 complete sequence, and thus, something we don't want
2450                 copied.
2451             */
2452             if (data[i] == cont_seq[cmatch])
2453             {
2454                 cmatch++;
2455                 if (cmatch == strlen(cont_seq))
2456                 {
2457                     cmatch = 0; // complete match.  just reset the counter
2458
2459                     /*
2460                         it's possible for the ICS to not include the space
2461                         at the end of the last word, making our [correct]
2462                         join operation fuse two separate words.  the server
2463                         does this when the space occurs at the width setting.
2464                     */
2465                     if (!buf_len || buf[buf_len-1] != ' ')
2466                     {
2467                         *bp++ = ' ';
2468                         buf_len++;
2469                     }
2470                 }
2471                 continue;
2472             }
2473             else if (cmatch)
2474             {
2475                 /*
2476                     match failed, so we have to copy what matched before
2477                     falling through and copying this character.  In reality,
2478                     this will only ever be just the newline character, but
2479                     it doesn't hurt to be precise.
2480                 */
2481                 strncpy(bp, cont_seq, cmatch);
2482                 bp += cmatch;
2483                 buf_len += cmatch;
2484                 cmatch = 0;
2485             }
2486         }
2487
2488         // copy this char
2489         *bp++ = data[i];
2490         buf_len++;
2491     }
2492
2493         buf[buf_len] = NULLCHAR;
2494 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2495         next_out = 0;
2496         leftover_start = 0;
2497
2498         i = 0;
2499         while (i < buf_len) {
2500             /* Deal with part of the TELNET option negotiation
2501                protocol.  We refuse to do anything beyond the
2502                defaults, except that we allow the WILL ECHO option,
2503                which ICS uses to turn off password echoing when we are
2504                directly connected to it.  We reject this option
2505                if localLineEditing mode is on (always on in xboard)
2506                and we are talking to port 23, which might be a real
2507                telnet server that will try to keep WILL ECHO on permanently.
2508              */
2509             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2510                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2511                 unsigned char option;
2512                 oldi = i;
2513                 switch ((unsigned char) buf[++i]) {
2514                   case TN_WILL:
2515                     if (appData.debugMode)
2516                       fprintf(debugFP, "\n<WILL ");
2517                     switch (option = (unsigned char) buf[++i]) {
2518                       case TN_ECHO:
2519                         if (appData.debugMode)
2520                           fprintf(debugFP, "ECHO ");
2521                         /* Reply only if this is a change, according
2522                            to the protocol rules. */
2523                         if (remoteEchoOption) break;
2524                         if (appData.localLineEditing &&
2525                             atoi(appData.icsPort) == TN_PORT) {
2526                             TelnetRequest(TN_DONT, TN_ECHO);
2527                         } else {
2528                             EchoOff();
2529                             TelnetRequest(TN_DO, TN_ECHO);
2530                             remoteEchoOption = TRUE;
2531                         }
2532                         break;
2533                       default:
2534                         if (appData.debugMode)
2535                           fprintf(debugFP, "%d ", option);
2536                         /* Whatever this is, we don't want it. */
2537                         TelnetRequest(TN_DONT, option);
2538                         break;
2539                     }
2540                     break;
2541                   case TN_WONT:
2542                     if (appData.debugMode)
2543                       fprintf(debugFP, "\n<WONT ");
2544                     switch (option = (unsigned char) buf[++i]) {
2545                       case TN_ECHO:
2546                         if (appData.debugMode)
2547                           fprintf(debugFP, "ECHO ");
2548                         /* Reply only if this is a change, according
2549                            to the protocol rules. */
2550                         if (!remoteEchoOption) break;
2551                         EchoOn();
2552                         TelnetRequest(TN_DONT, TN_ECHO);
2553                         remoteEchoOption = FALSE;
2554                         break;
2555                       default:
2556                         if (appData.debugMode)
2557                           fprintf(debugFP, "%d ", (unsigned char) option);
2558                         /* Whatever this is, it must already be turned
2559                            off, because we never agree to turn on
2560                            anything non-default, so according to the
2561                            protocol rules, we don't reply. */
2562                         break;
2563                     }
2564                     break;
2565                   case TN_DO:
2566                     if (appData.debugMode)
2567                       fprintf(debugFP, "\n<DO ");
2568                     switch (option = (unsigned char) buf[++i]) {
2569                       default:
2570                         /* Whatever this is, we refuse to do it. */
2571                         if (appData.debugMode)
2572                           fprintf(debugFP, "%d ", option);
2573                         TelnetRequest(TN_WONT, option);
2574                         break;
2575                     }
2576                     break;
2577                   case TN_DONT:
2578                     if (appData.debugMode)
2579                       fprintf(debugFP, "\n<DONT ");
2580                     switch (option = (unsigned char) buf[++i]) {
2581                       default:
2582                         if (appData.debugMode)
2583                           fprintf(debugFP, "%d ", option);
2584                         /* Whatever this is, we are already not doing
2585                            it, because we never agree to do anything
2586                            non-default, so according to the protocol
2587                            rules, we don't reply. */
2588                         break;
2589                     }
2590                     break;
2591                   case TN_IAC:
2592                     if (appData.debugMode)
2593                       fprintf(debugFP, "\n<IAC ");
2594                     /* Doubled IAC; pass it through */
2595                     i--;
2596                     break;
2597                   default:
2598                     if (appData.debugMode)
2599                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2600                     /* Drop all other telnet commands on the floor */
2601                     break;
2602                 }
2603                 if (oldi > next_out)
2604                   SendToPlayer(&buf[next_out], oldi - next_out);
2605                 if (++i > next_out)
2606                   next_out = i;
2607                 continue;
2608             }
2609
2610             /* OK, this at least will *usually* work */
2611             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2612                 loggedOn = TRUE;
2613             }
2614
2615             if (loggedOn && !intfSet) {
2616                 if (ics_type == ICS_ICC) {
2617                   snprintf(str, MSG_SIZ,
2618                           "/set-quietly interface %s\n/set-quietly style 12\n",
2619                           programVersion);
2620                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2621                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2622                 } else if (ics_type == ICS_CHESSNET) {
2623                   snprintf(str, MSG_SIZ, "/style 12\n");
2624                 } else {
2625                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2626                   strcat(str, programVersion);
2627                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2628                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2629                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2630 #ifdef WIN32
2631                   strcat(str, "$iset nohighlight 1\n");
2632 #endif
2633                   strcat(str, "$iset lock 1\n$style 12\n");
2634                 }
2635                 SendToICS(str);
2636                 NotifyFrontendLogin();
2637                 intfSet = TRUE;
2638             }
2639
2640             if (started == STARTED_COMMENT) {
2641                 /* Accumulate characters in comment */
2642                 parse[parse_pos++] = buf[i];
2643                 if (buf[i] == '\n') {
2644                     parse[parse_pos] = NULLCHAR;
2645                     if(chattingPartner>=0) {
2646                         char mess[MSG_SIZ];
2647                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2648                         OutputChatMessage(chattingPartner, mess);
2649                         chattingPartner = -1;
2650                         next_out = i+1; // [HGM] suppress printing in ICS window
2651                     } else
2652                     if(!suppressKibitz) // [HGM] kibitz
2653                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2654                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2655                         int nrDigit = 0, nrAlph = 0, j;
2656                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2657                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2658                         parse[parse_pos] = NULLCHAR;
2659                         // try to be smart: if it does not look like search info, it should go to
2660                         // ICS interaction window after all, not to engine-output window.
2661                         for(j=0; j<parse_pos; j++) { // count letters and digits
2662                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2663                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2664                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2665                         }
2666                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2667                             int depth=0; float score;
2668                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2669                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2670                                 pvInfoList[forwardMostMove-1].depth = depth;
2671                                 pvInfoList[forwardMostMove-1].score = 100*score;
2672                             }
2673                             OutputKibitz(suppressKibitz, parse);
2674                         } else {
2675                             char tmp[MSG_SIZ];
2676                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2677                             SendToPlayer(tmp, strlen(tmp));
2678                         }
2679                         next_out = i+1; // [HGM] suppress printing in ICS window
2680                     }
2681                     started = STARTED_NONE;
2682                 } else {
2683                     /* Don't match patterns against characters in comment */
2684                     i++;
2685                     continue;
2686                 }
2687             }
2688             if (started == STARTED_CHATTER) {
2689                 if (buf[i] != '\n') {
2690                     /* Don't match patterns against characters in chatter */
2691                     i++;
2692                     continue;
2693                 }
2694                 started = STARTED_NONE;
2695                 if(suppressKibitz) next_out = i+1;
2696             }
2697
2698             /* Kludge to deal with rcmd protocol */
2699             if (firstTime && looking_at(buf, &i, "\001*")) {
2700                 DisplayFatalError(&buf[1], 0, 1);
2701                 continue;
2702             } else {
2703                 firstTime = FALSE;
2704             }
2705
2706             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2707                 ics_type = ICS_ICC;
2708                 ics_prefix = "/";
2709                 if (appData.debugMode)
2710                   fprintf(debugFP, "ics_type %d\n", ics_type);
2711                 continue;
2712             }
2713             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2714                 ics_type = ICS_FICS;
2715                 ics_prefix = "$";
2716                 if (appData.debugMode)
2717                   fprintf(debugFP, "ics_type %d\n", ics_type);
2718                 continue;
2719             }
2720             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2721                 ics_type = ICS_CHESSNET;
2722                 ics_prefix = "/";
2723                 if (appData.debugMode)
2724                   fprintf(debugFP, "ics_type %d\n", ics_type);
2725                 continue;
2726             }
2727
2728             if (!loggedOn &&
2729                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2730                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2731                  looking_at(buf, &i, "will be \"*\""))) {
2732               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2733               continue;
2734             }
2735
2736             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2737               char buf[MSG_SIZ];
2738               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2739               DisplayIcsInteractionTitle(buf);
2740               have_set_title = TRUE;
2741             }
2742
2743             /* skip finger notes */
2744             if (started == STARTED_NONE &&
2745                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2746                  (buf[i] == '1' && buf[i+1] == '0')) &&
2747                 buf[i+2] == ':' && buf[i+3] == ' ') {
2748               started = STARTED_CHATTER;
2749               i += 3;
2750               continue;
2751             }
2752
2753             oldi = i;
2754             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2755             if(appData.seekGraph) {
2756                 if(soughtPending && MatchSoughtLine(buf+i)) {
2757                     i = strstr(buf+i, "rated") - buf;
2758                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2759                     next_out = leftover_start = i;
2760                     started = STARTED_CHATTER;
2761                     suppressKibitz = TRUE;
2762                     continue;
2763                 }
2764                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2765                         && looking_at(buf, &i, "* ads displayed")) {
2766                     soughtPending = FALSE;
2767                     seekGraphUp = TRUE;
2768                     DrawSeekGraph();
2769                     continue;
2770                 }
2771                 if(appData.autoRefresh) {
2772                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2773                         int s = (ics_type == ICS_ICC); // ICC format differs
2774                         if(seekGraphUp)
2775                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2776                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2777                         looking_at(buf, &i, "*% "); // eat prompt
2778                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2779                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2780                         next_out = i; // suppress
2781                         continue;
2782                     }
2783                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2784                         char *p = star_match[0];
2785                         while(*p) {
2786                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2787                             while(*p && *p++ != ' '); // next
2788                         }
2789                         looking_at(buf, &i, "*% "); // eat prompt
2790                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2791                         next_out = i;
2792                         continue;
2793                     }
2794                 }
2795             }
2796
2797             /* skip formula vars */
2798             if (started == STARTED_NONE &&
2799                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2800               started = STARTED_CHATTER;
2801               i += 3;
2802               continue;
2803             }
2804
2805             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2806             if (appData.autoKibitz && started == STARTED_NONE &&
2807                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2808                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2809                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2810                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2811                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2812                         suppressKibitz = TRUE;
2813                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2814                         next_out = i;
2815                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2816                                 && (gameMode == IcsPlayingWhite)) ||
2817                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2818                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2819                             started = STARTED_CHATTER; // own kibitz we simply discard
2820                         else {
2821                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2822                             parse_pos = 0; parse[0] = NULLCHAR;
2823                             savingComment = TRUE;
2824                             suppressKibitz = gameMode != IcsObserving ? 2 :
2825                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2826                         }
2827                         continue;
2828                 } else
2829                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2830                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2831                          && atoi(star_match[0])) {
2832                     // suppress the acknowledgements of our own autoKibitz
2833                     char *p;
2834                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2835                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2836                     SendToPlayer(star_match[0], strlen(star_match[0]));
2837                     if(looking_at(buf, &i, "*% ")) // eat prompt
2838                         suppressKibitz = FALSE;
2839                     next_out = i;
2840                     continue;
2841                 }
2842             } // [HGM] kibitz: end of patch
2843
2844             // [HGM] chat: intercept tells by users for which we have an open chat window
2845             channel = -1;
2846             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2847                                            looking_at(buf, &i, "* whispers:") ||
2848                                            looking_at(buf, &i, "* kibitzes:") ||
2849                                            looking_at(buf, &i, "* shouts:") ||
2850                                            looking_at(buf, &i, "* c-shouts:") ||
2851                                            looking_at(buf, &i, "--> * ") ||
2852                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2853                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2854                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2855                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2856                 int p;
2857                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2858                 chattingPartner = -1;
2859
2860                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2861                 for(p=0; p<MAX_CHAT; p++) {
2862                     if(channel == atoi(chatPartner[p])) {
2863                     talker[0] = '['; strcat(talker, "] ");
2864                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2865                     chattingPartner = p; break;
2866                     }
2867                 } else
2868                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2869                 for(p=0; p<MAX_CHAT; p++) {
2870                     if(!strcmp("kibitzes", chatPartner[p])) {
2871                         talker[0] = '['; strcat(talker, "] ");
2872                         chattingPartner = p; break;
2873                     }
2874                 } else
2875                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2876                 for(p=0; p<MAX_CHAT; p++) {
2877                     if(!strcmp("whispers", chatPartner[p])) {
2878                         talker[0] = '['; strcat(talker, "] ");
2879                         chattingPartner = p; break;
2880                     }
2881                 } else
2882                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2883                   if(buf[i-8] == '-' && buf[i-3] == 't')
2884                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2885                     if(!strcmp("c-shouts", chatPartner[p])) {
2886                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2887                         chattingPartner = p; break;
2888                     }
2889                   }
2890                   if(chattingPartner < 0)
2891                   for(p=0; p<MAX_CHAT; p++) {
2892                     if(!strcmp("shouts", chatPartner[p])) {
2893                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2894                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2895                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2896                         chattingPartner = p; break;
2897                     }
2898                   }
2899                 }
2900                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2901                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2902                     talker[0] = 0; Colorize(ColorTell, FALSE);
2903                     chattingPartner = p; break;
2904                 }
2905                 if(chattingPartner<0) i = oldi; else {
2906                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2907                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2908                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2909                     started = STARTED_COMMENT;
2910                     parse_pos = 0; parse[0] = NULLCHAR;
2911                     savingComment = 3 + chattingPartner; // counts as TRUE
2912                     suppressKibitz = TRUE;
2913                     continue;
2914                 }
2915             } // [HGM] chat: end of patch
2916
2917             if (appData.zippyTalk || appData.zippyPlay) {
2918                 /* [DM] Backup address for color zippy lines */
2919                 backup = i;
2920 #if ZIPPY
2921                if (loggedOn == TRUE)
2922                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2923                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2924 #endif
2925             } // [DM] 'else { ' deleted
2926                 if (
2927                     /* Regular tells and says */
2928                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2929                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2930                     looking_at(buf, &i, "* says: ") ||
2931                     /* Don't color "message" or "messages" output */
2932                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2933                     looking_at(buf, &i, "*. * at *:*: ") ||
2934                     looking_at(buf, &i, "--* (*:*): ") ||
2935                     /* Message notifications (same color as tells) */
2936                     looking_at(buf, &i, "* has left a message ") ||
2937                     looking_at(buf, &i, "* just sent you a message:\n") ||
2938                     /* Whispers and kibitzes */
2939                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2940                     looking_at(buf, &i, "* kibitzes: ") ||
2941                     /* Channel tells */
2942                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2943
2944                   if (tkind == 1 && strchr(star_match[0], ':')) {
2945                       /* Avoid "tells you:" spoofs in channels */
2946                      tkind = 3;
2947                   }
2948                   if (star_match[0][0] == NULLCHAR ||
2949                       strchr(star_match[0], ' ') ||
2950                       (tkind == 3 && strchr(star_match[1], ' '))) {
2951                     /* Reject bogus matches */
2952                     i = oldi;
2953                   } else {
2954                     if (appData.colorize) {
2955                       if (oldi > next_out) {
2956                         SendToPlayer(&buf[next_out], oldi - next_out);
2957                         next_out = oldi;
2958                       }
2959                       switch (tkind) {
2960                       case 1:
2961                         Colorize(ColorTell, FALSE);
2962                         curColor = ColorTell;
2963                         break;
2964                       case 2:
2965                         Colorize(ColorKibitz, FALSE);
2966                         curColor = ColorKibitz;
2967                         break;
2968                       case 3:
2969                         p = strrchr(star_match[1], '(');
2970                         if (p == NULL) {
2971                           p = star_match[1];
2972                         } else {
2973                           p++;
2974                         }
2975                         if (atoi(p) == 1) {
2976                           Colorize(ColorChannel1, FALSE);
2977                           curColor = ColorChannel1;
2978                         } else {
2979                           Colorize(ColorChannel, FALSE);
2980                           curColor = ColorChannel;
2981                         }
2982                         break;
2983                       case 5:
2984                         curColor = ColorNormal;
2985                         break;
2986                       }
2987                     }
2988                     if (started == STARTED_NONE && appData.autoComment &&
2989                         (gameMode == IcsObserving ||
2990                          gameMode == IcsPlayingWhite ||
2991                          gameMode == IcsPlayingBlack)) {
2992                       parse_pos = i - oldi;
2993                       memcpy(parse, &buf[oldi], parse_pos);
2994                       parse[parse_pos] = NULLCHAR;
2995                       started = STARTED_COMMENT;
2996                       savingComment = TRUE;
2997                     } else {
2998                       started = STARTED_CHATTER;
2999                       savingComment = FALSE;
3000                     }
3001                     loggedOn = TRUE;
3002                     continue;
3003                   }
3004                 }
3005
3006                 if (looking_at(buf, &i, "* s-shouts: ") ||
3007                     looking_at(buf, &i, "* c-shouts: ")) {
3008                     if (appData.colorize) {
3009                         if (oldi > next_out) {
3010                             SendToPlayer(&buf[next_out], oldi - next_out);
3011                             next_out = oldi;
3012                         }
3013                         Colorize(ColorSShout, FALSE);
3014                         curColor = ColorSShout;
3015                     }
3016                     loggedOn = TRUE;
3017                     started = STARTED_CHATTER;
3018                     continue;
3019                 }
3020
3021                 if (looking_at(buf, &i, "--->")) {
3022                     loggedOn = TRUE;
3023                     continue;
3024                 }
3025
3026                 if (looking_at(buf, &i, "* shouts: ") ||
3027                     looking_at(buf, &i, "--> ")) {
3028                     if (appData.colorize) {
3029                         if (oldi > next_out) {
3030                             SendToPlayer(&buf[next_out], oldi - next_out);
3031                             next_out = oldi;
3032                         }
3033                         Colorize(ColorShout, FALSE);
3034                         curColor = ColorShout;
3035                     }
3036                     loggedOn = TRUE;
3037                     started = STARTED_CHATTER;
3038                     continue;
3039                 }
3040
3041                 if (looking_at( buf, &i, "Challenge:")) {
3042                     if (appData.colorize) {
3043                         if (oldi > next_out) {
3044                             SendToPlayer(&buf[next_out], oldi - next_out);
3045                             next_out = oldi;
3046                         }
3047                         Colorize(ColorChallenge, FALSE);
3048                         curColor = ColorChallenge;
3049                     }
3050                     loggedOn = TRUE;
3051                     continue;
3052                 }
3053
3054                 if (looking_at(buf, &i, "* offers you") ||
3055                     looking_at(buf, &i, "* offers to be") ||
3056                     looking_at(buf, &i, "* would like to") ||
3057                     looking_at(buf, &i, "* requests to") ||
3058                     looking_at(buf, &i, "Your opponent offers") ||
3059                     looking_at(buf, &i, "Your opponent requests")) {
3060
3061                     if (appData.colorize) {
3062                         if (oldi > next_out) {
3063                             SendToPlayer(&buf[next_out], oldi - next_out);
3064                             next_out = oldi;
3065                         }
3066                         Colorize(ColorRequest, FALSE);
3067                         curColor = ColorRequest;
3068                     }
3069                     continue;
3070                 }
3071
3072                 if (looking_at(buf, &i, "* (*) seeking")) {
3073                     if (appData.colorize) {
3074                         if (oldi > next_out) {
3075                             SendToPlayer(&buf[next_out], oldi - next_out);
3076                             next_out = oldi;
3077                         }
3078                         Colorize(ColorSeek, FALSE);
3079                         curColor = ColorSeek;
3080                     }
3081                     continue;
3082             }
3083
3084             if (looking_at(buf, &i, "\\   ")) {
3085                 if (prevColor != ColorNormal) {
3086                     if (oldi > next_out) {
3087                         SendToPlayer(&buf[next_out], oldi - next_out);
3088                         next_out = oldi;
3089                     }
3090                     Colorize(prevColor, TRUE);
3091                     curColor = prevColor;
3092                 }
3093                 if (savingComment) {
3094                     parse_pos = i - oldi;
3095                     memcpy(parse, &buf[oldi], parse_pos);
3096                     parse[parse_pos] = NULLCHAR;
3097                     started = STARTED_COMMENT;
3098                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3099                         chattingPartner = savingComment - 3; // kludge to remember the box
3100                 } else {
3101                     started = STARTED_CHATTER;
3102                 }
3103                 continue;
3104             }
3105
3106             if (looking_at(buf, &i, "Black Strength :") ||
3107                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3108                 looking_at(buf, &i, "<10>") ||
3109                 looking_at(buf, &i, "#@#")) {
3110                 /* Wrong board style */
3111                 loggedOn = TRUE;
3112                 SendToICS(ics_prefix);
3113                 SendToICS("set style 12\n");
3114                 SendToICS(ics_prefix);
3115                 SendToICS("refresh\n");
3116                 continue;
3117             }
3118
3119             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3120                 ICSInitScript();
3121                 have_sent_ICS_logon = 1;
3122                 /* if we don't send the login/password via icsLogon, use special readline
3123                    code for it */
3124                 if (strlen(appData.icsLogon)==0)
3125                   {
3126                     sending_ICS_password = 0; // in case we come back to login
3127                     sending_ICS_login = 1;
3128                   };
3129                 continue;
3130             }
3131             /* need to shadow the password */
3132             if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3133               /* if we don't send the login/password via icsLogon, use special readline
3134                  code for it */
3135               if (strlen(appData.icsLogon)==0)
3136                 sending_ICS_password = 1;
3137               continue;
3138             }
3139
3140             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3141                 (looking_at(buf, &i, "\n<12> ") ||
3142                  looking_at(buf, &i, "<12> "))) {
3143                 loggedOn = TRUE;
3144                 if (oldi > next_out) {
3145                     SendToPlayer(&buf[next_out], oldi - next_out);
3146                 }
3147                 next_out = i;
3148                 started = STARTED_BOARD;
3149                 parse_pos = 0;
3150                 continue;
3151             }
3152
3153             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3154                 looking_at(buf, &i, "<b1> ")) {
3155                 if (oldi > next_out) {
3156                     SendToPlayer(&buf[next_out], oldi - next_out);
3157                 }
3158                 next_out = i;
3159                 started = STARTED_HOLDINGS;
3160                 parse_pos = 0;
3161                 continue;
3162             }
3163
3164             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3165                 loggedOn = TRUE;
3166                 /* Header for a move list -- first line */
3167
3168                 switch (ics_getting_history) {
3169                   case H_FALSE:
3170                     switch (gameMode) {
3171                       case IcsIdle:
3172                       case BeginningOfGame:
3173                         /* User typed "moves" or "oldmoves" while we
3174                            were idle.  Pretend we asked for these
3175                            moves and soak them up so user can step
3176                            through them and/or save them.
3177                            */
3178                         Reset(FALSE, TRUE);
3179                         gameMode = IcsObserving;
3180                         ModeHighlight();
3181                         ics_gamenum = -1;
3182                         ics_getting_history = H_GOT_UNREQ_HEADER;
3183                         break;
3184                       case EditGame: /*?*/
3185                       case EditPosition: /*?*/
3186                         /* Should above feature work in these modes too? */
3187                         /* For now it doesn't */
3188                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3189                         break;
3190                       default:
3191                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3192                         break;
3193                     }
3194                     break;
3195                   case H_REQUESTED:
3196                     /* Is this the right one? */
3197                     if (gameInfo.white && gameInfo.black &&
3198                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3199                         strcmp(gameInfo.black, star_match[2]) == 0) {
3200                         /* All is well */
3201                         ics_getting_history = H_GOT_REQ_HEADER;
3202                     }
3203                     break;
3204                   case H_GOT_REQ_HEADER:
3205                   case H_GOT_UNREQ_HEADER:
3206                   case H_GOT_UNWANTED_HEADER:
3207                   case H_GETTING_MOVES:
3208                     /* Should not happen */
3209                     DisplayError(_("Error gathering move list: two headers"), 0);
3210                     ics_getting_history = H_FALSE;
3211                     break;
3212                 }
3213
3214                 /* Save player ratings into gameInfo if needed */
3215                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3216                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3217                     (gameInfo.whiteRating == -1 ||
3218                      gameInfo.blackRating == -1)) {
3219
3220                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3221                     gameInfo.blackRating = string_to_rating(star_match[3]);
3222                     if (appData.debugMode)
3223                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3224                               gameInfo.whiteRating, gameInfo.blackRating);
3225                 }
3226                 continue;
3227             }
3228
3229             if (looking_at(buf, &i,
3230               "* * match, initial time: * minute*, increment: * second")) {
3231                 /* Header for a move list -- second line */
3232                 /* Initial board will follow if this is a wild game */
3233                 if (gameInfo.event != NULL) free(gameInfo.event);
3234                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3235                 gameInfo.event = StrSave(str);
3236                 /* [HGM] we switched variant. Translate boards if needed. */
3237                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3238                 continue;
3239             }
3240
3241             if (looking_at(buf, &i, "Move  ")) {
3242                 /* Beginning of a move list */
3243                 switch (ics_getting_history) {
3244                   case H_FALSE:
3245                     /* Normally should not happen */
3246                     /* Maybe user hit reset while we were parsing */
3247                     break;
3248                   case H_REQUESTED:
3249                     /* Happens if we are ignoring a move list that is not
3250                      * the one we just requested.  Common if the user
3251                      * tries to observe two games without turning off
3252                      * getMoveList */
3253                     break;
3254                   case H_GETTING_MOVES:
3255                     /* Should not happen */
3256                     DisplayError(_("Error gathering move list: nested"), 0);
3257                     ics_getting_history = H_FALSE;
3258                     break;
3259                   case H_GOT_REQ_HEADER:
3260                     ics_getting_history = H_GETTING_MOVES;
3261                     started = STARTED_MOVES;
3262                     parse_pos = 0;
3263                     if (oldi > next_out) {
3264                         SendToPlayer(&buf[next_out], oldi - next_out);
3265                     }
3266                     break;
3267                   case H_GOT_UNREQ_HEADER:
3268                     ics_getting_history = H_GETTING_MOVES;
3269                     started = STARTED_MOVES_NOHIDE;
3270                     parse_pos = 0;
3271                     break;
3272                   case H_GOT_UNWANTED_HEADER:
3273                     ics_getting_history = H_FALSE;
3274                     break;
3275                 }
3276                 continue;
3277             }
3278
3279             if (looking_at(buf, &i, "% ") ||
3280                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3281                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3282                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3283                     soughtPending = FALSE;
3284                     seekGraphUp = TRUE;
3285                     DrawSeekGraph();
3286                 }
3287                 if(suppressKibitz) next_out = i;
3288                 savingComment = FALSE;
3289                 suppressKibitz = 0;
3290                 switch (started) {
3291                   case STARTED_MOVES:
3292                   case STARTED_MOVES_NOHIDE:
3293                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3294                     parse[parse_pos + i - oldi] = NULLCHAR;
3295                     ParseGameHistory(parse);
3296 #if ZIPPY
3297                     if (appData.zippyPlay && first.initDone) {
3298                         FeedMovesToProgram(&first, forwardMostMove);
3299                         if (gameMode == IcsPlayingWhite) {
3300                             if (WhiteOnMove(forwardMostMove)) {
3301                                 if (first.sendTime) {
3302                                   if (first.useColors) {
3303                                     SendToProgram("black\n", &first);
3304                                   }
3305                                   SendTimeRemaining(&first, TRUE);
3306                                 }
3307                                 if (first.useColors) {
3308                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3309                                 }
3310                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3311                                 first.maybeThinking = TRUE;
3312                             } else {
3313                                 if (first.usePlayother) {
3314                                   if (first.sendTime) {
3315                                     SendTimeRemaining(&first, TRUE);
3316                                   }
3317                                   SendToProgram("playother\n", &first);
3318                                   firstMove = FALSE;
3319                                 } else {
3320                                   firstMove = TRUE;
3321                                 }
3322                             }
3323                         } else if (gameMode == IcsPlayingBlack) {
3324                             if (!WhiteOnMove(forwardMostMove)) {
3325                                 if (first.sendTime) {
3326                                   if (first.useColors) {
3327                                     SendToProgram("white\n", &first);
3328                                   }
3329                                   SendTimeRemaining(&first, FALSE);
3330                                 }
3331                                 if (first.useColors) {
3332                                   SendToProgram("black\n", &first);
3333                                 }
3334                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3335                                 first.maybeThinking = TRUE;
3336                             } else {
3337                                 if (first.usePlayother) {
3338                                   if (first.sendTime) {
3339                                     SendTimeRemaining(&first, FALSE);
3340                                   }
3341                                   SendToProgram("playother\n", &first);
3342                                   firstMove = FALSE;
3343                                 } else {
3344                                   firstMove = TRUE;
3345                                 }
3346                             }
3347                         }
3348                     }
3349 #endif
3350                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3351                         /* Moves came from oldmoves or moves command
3352                            while we weren't doing anything else.
3353                            */
3354                         currentMove = forwardMostMove;
3355                         ClearHighlights();/*!!could figure this out*/
3356                         flipView = appData.flipView;
3357                         DrawPosition(TRUE, boards[currentMove]);
3358                         DisplayBothClocks();
3359                         snprintf(str, MSG_SIZ, "%s vs. %s",
3360                                 gameInfo.white, gameInfo.black);
3361                         DisplayTitle(str);
3362                         gameMode = IcsIdle;
3363                     } else {
3364                         /* Moves were history of an active game */
3365                         if (gameInfo.resultDetails != NULL) {
3366                             free(gameInfo.resultDetails);
3367                             gameInfo.resultDetails = NULL;
3368                         }
3369                     }
3370                     HistorySet(parseList, backwardMostMove,
3371                                forwardMostMove, currentMove-1);
3372                     DisplayMove(currentMove - 1);
3373                     if (started == STARTED_MOVES) next_out = i;
3374                     started = STARTED_NONE;
3375                     ics_getting_history = H_FALSE;
3376                     break;
3377
3378                   case STARTED_OBSERVE:
3379                     started = STARTED_NONE;
3380                     SendToICS(ics_prefix);
3381                     SendToICS("refresh\n");
3382                     break;
3383
3384                   default:
3385                     break;
3386                 }
3387                 if(bookHit) { // [HGM] book: simulate book reply
3388                     static char bookMove[MSG_SIZ]; // a bit generous?
3389
3390                     programStats.nodes = programStats.depth = programStats.time =
3391                     programStats.score = programStats.got_only_move = 0;
3392                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3393
3394                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3395                     strcat(bookMove, bookHit);
3396                     HandleMachineMove(bookMove, &first);
3397                 }
3398                 continue;
3399             }
3400
3401             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3402                  started == STARTED_HOLDINGS ||
3403                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3404                 /* Accumulate characters in move list or board */
3405                 parse[parse_pos++] = buf[i];
3406             }
3407
3408             /* Start of game messages.  Mostly we detect start of game
3409                when the first board image arrives.  On some versions
3410                of the ICS, though, we need to do a "refresh" after starting
3411                to observe in order to get the current board right away. */
3412             if (looking_at(buf, &i, "Adding game * to observation list")) {
3413                 started = STARTED_OBSERVE;
3414                 continue;
3415             }
3416
3417             /* Handle auto-observe */
3418             if (appData.autoObserve &&
3419                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3420                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3421                 char *player;
3422                 /* Choose the player that was highlighted, if any. */
3423                 if (star_match[0][0] == '\033' ||
3424                     star_match[1][0] != '\033') {
3425                     player = star_match[0];
3426                 } else {
3427                     player = star_match[2];
3428                 }
3429                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3430                         ics_prefix, StripHighlightAndTitle(player));
3431                 SendToICS(str);
3432
3433                 /* Save ratings from notify string */
3434                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3435                 player1Rating = string_to_rating(star_match[1]);
3436                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3437                 player2Rating = string_to_rating(star_match[3]);
3438
3439                 if (appData.debugMode)
3440                   fprintf(debugFP,
3441                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3442                           player1Name, player1Rating,
3443                           player2Name, player2Rating);
3444
3445                 continue;
3446             }
3447
3448             /* Deal with automatic examine mode after a game,
3449                and with IcsObserving -> IcsExamining transition */
3450             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3451                 looking_at(buf, &i, "has made you an examiner of game *")) {
3452
3453                 int gamenum = atoi(star_match[0]);
3454                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3455                     gamenum == ics_gamenum) {
3456                     /* We were already playing or observing this game;
3457                        no need to refetch history */
3458                     gameMode = IcsExamining;
3459                     if (pausing) {
3460                         pauseExamForwardMostMove = forwardMostMove;
3461                     } else if (currentMove < forwardMostMove) {
3462                         ForwardInner(forwardMostMove);
3463                     }
3464                 } else {
3465                     /* I don't think this case really can happen */
3466                     SendToICS(ics_prefix);
3467                     SendToICS("refresh\n");
3468                 }
3469                 continue;
3470             }
3471
3472             /* Error messages */
3473 //          if (ics_user_moved) {
3474             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3475                 if (looking_at(buf, &i, "Illegal move") ||
3476                     looking_at(buf, &i, "Not a legal move") ||
3477                     looking_at(buf, &i, "Your king is in check") ||
3478                     looking_at(buf, &i, "It isn't your turn") ||
3479                     looking_at(buf, &i, "It is not your move")) {
3480                     /* Illegal move */
3481                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3482                         currentMove = forwardMostMove-1;
3483                         DisplayMove(currentMove - 1); /* before DMError */
3484                         DrawPosition(FALSE, boards[currentMove]);
3485                         SwitchClocks(forwardMostMove-1); // [HGM] race
3486                         DisplayBothClocks();
3487                     }
3488                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3489                     ics_user_moved = 0;
3490                     continue;
3491                 }
3492             }
3493
3494             if (looking_at(buf, &i, "still have time") ||
3495                 looking_at(buf, &i, "not out of time") ||
3496                 looking_at(buf, &i, "either player is out of time") ||
3497                 looking_at(buf, &i, "has timeseal; checking")) {
3498                 /* We must have called his flag a little too soon */
3499                 whiteFlag = blackFlag = FALSE;
3500                 continue;
3501             }
3502
3503             if (looking_at(buf, &i, "added * seconds to") ||
3504                 looking_at(buf, &i, "seconds were added to")) {
3505                 /* Update the clocks */
3506                 SendToICS(ics_prefix);
3507                 SendToICS("refresh\n");
3508                 continue;
3509             }
3510
3511             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3512                 ics_clock_paused = TRUE;
3513                 StopClocks();
3514                 continue;
3515             }
3516
3517             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3518                 ics_clock_paused = FALSE;
3519                 StartClocks();
3520                 continue;
3521             }
3522
3523             /* Grab player ratings from the Creating: message.
3524                Note we have to check for the special case when
3525                the ICS inserts things like [white] or [black]. */
3526             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3527                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3528                 /* star_matches:
3529                    0    player 1 name (not necessarily white)
3530                    1    player 1 rating
3531                    2    empty, white, or black (IGNORED)
3532                    3    player 2 name (not necessarily black)
3533                    4    player 2 rating
3534
3535                    The names/ratings are sorted out when the game
3536                    actually starts (below).
3537                 */
3538                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3539                 player1Rating = string_to_rating(star_match[1]);
3540                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3541                 player2Rating = string_to_rating(star_match[4]);
3542
3543                 if (appData.debugMode)
3544                   fprintf(debugFP,
3545                           "Ratings from 'Creating:' %s %d, %s %d\n",
3546                           player1Name, player1Rating,
3547                           player2Name, player2Rating);
3548
3549                 continue;
3550             }
3551
3552             /* Improved generic start/end-of-game messages */
3553             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3554                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3555                 /* If tkind == 0: */
3556                 /* star_match[0] is the game number */
3557                 /*           [1] is the white player's name */
3558                 /*           [2] is the black player's name */
3559                 /* For end-of-game: */
3560                 /*           [3] is the reason for the game end */
3561                 /*           [4] is a PGN end game-token, preceded by " " */
3562                 /* For start-of-game: */
3563                 /*           [3] begins with "Creating" or "Continuing" */
3564                 /*           [4] is " *" or empty (don't care). */
3565                 int gamenum = atoi(star_match[0]);
3566                 char *whitename, *blackname, *why, *endtoken;
3567                 ChessMove endtype = EndOfFile;
3568
3569                 if (tkind == 0) {
3570                   whitename = star_match[1];
3571                   blackname = star_match[2];
3572                   why = star_match[3];
3573                   endtoken = star_match[4];
3574                 } else {
3575                   whitename = star_match[1];
3576                   blackname = star_match[3];
3577                   why = star_match[5];
3578                   endtoken = star_match[6];
3579                 }
3580
3581                 /* Game start messages */
3582                 if (strncmp(why, "Creating ", 9) == 0 ||
3583                     strncmp(why, "Continuing ", 11) == 0) {
3584                     gs_gamenum = gamenum;
3585                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3586                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3587 #if ZIPPY
3588                     if (appData.zippyPlay) {
3589                         ZippyGameStart(whitename, blackname);
3590                     }
3591 #endif /*ZIPPY*/
3592                     partnerBoardValid = FALSE; // [HGM] bughouse
3593                     continue;
3594                 }
3595
3596                 /* Game end messages */
3597                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3598                     ics_gamenum != gamenum) {
3599                     continue;
3600                 }
3601                 while (endtoken[0] == ' ') endtoken++;
3602                 switch (endtoken[0]) {
3603                   case '*':
3604                   default:
3605                     endtype = GameUnfinished;
3606                     break;
3607                   case '0':
3608                     endtype = BlackWins;
3609                     break;
3610                   case '1':
3611                     if (endtoken[1] == '/')
3612                       endtype = GameIsDrawn;
3613                     else
3614                       endtype = WhiteWins;
3615                     break;
3616                 }
3617                 GameEnds(endtype, why, GE_ICS);
3618 #if ZIPPY
3619                 if (appData.zippyPlay && first.initDone) {
3620                     ZippyGameEnd(endtype, why);
3621                     if (first.pr == NULL) {
3622                       /* Start the next process early so that we'll
3623                          be ready for the next challenge */
3624                       StartChessProgram(&first);
3625                     }
3626                     /* Send "new" early, in case this command takes
3627                        a long time to finish, so that we'll be ready
3628                        for the next challenge. */
3629                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3630                     Reset(TRUE, TRUE);
3631                 }
3632 #endif /*ZIPPY*/
3633                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3634                 continue;
3635             }
3636
3637             if (looking_at(buf, &i, "Removing game * from observation") ||
3638                 looking_at(buf, &i, "no longer observing game *") ||
3639                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3640                 if (gameMode == IcsObserving &&
3641                     atoi(star_match[0]) == ics_gamenum)
3642                   {
3643                       /* icsEngineAnalyze */
3644                       if (appData.icsEngineAnalyze) {
3645                             ExitAnalyzeMode();
3646                             ModeHighlight();
3647                       }
3648                       StopClocks();
3649                       gameMode = IcsIdle;
3650                       ics_gamenum = -1;
3651                       ics_user_moved = FALSE;
3652                   }
3653                 continue;
3654             }
3655
3656             if (looking_at(buf, &i, "no longer examining game *")) {
3657                 if (gameMode == IcsExamining &&
3658                     atoi(star_match[0]) == ics_gamenum)
3659                   {
3660                       gameMode = IcsIdle;
3661                       ics_gamenum = -1;
3662                       ics_user_moved = FALSE;
3663                   }
3664                 continue;
3665             }
3666
3667             /* Advance leftover_start past any newlines we find,
3668                so only partial lines can get reparsed */
3669             if (looking_at(buf, &i, "\n")) {
3670                 prevColor = curColor;
3671                 if (curColor != ColorNormal) {
3672                     if (oldi > next_out) {
3673                         SendToPlayer(&buf[next_out], oldi - next_out);
3674                         next_out = oldi;
3675                     }
3676                     Colorize(ColorNormal, FALSE);
3677                     curColor = ColorNormal;
3678                 }
3679                 if (started == STARTED_BOARD) {
3680                     started = STARTED_NONE;
3681                     parse[parse_pos] = NULLCHAR;
3682                     ParseBoard12(parse);
3683                     ics_user_moved = 0;
3684
3685                     /* Send premove here */
3686                     if (appData.premove) {
3687                       char str[MSG_SIZ];
3688                       if (currentMove == 0 &&
3689                           gameMode == IcsPlayingWhite &&
3690                           appData.premoveWhite) {
3691                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3692                         if (appData.debugMode)
3693                           fprintf(debugFP, "Sending premove:\n");
3694                         SendToICS(str);
3695                       } else if (currentMove == 1 &&
3696                                  gameMode == IcsPlayingBlack &&
3697                                  appData.premoveBlack) {
3698                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3699                         if (appData.debugMode)
3700                           fprintf(debugFP, "Sending premove:\n");
3701                         SendToICS(str);
3702                       } else if (gotPremove) {
3703                         gotPremove = 0;
3704                         ClearPremoveHighlights();
3705                         if (appData.debugMode)
3706                           fprintf(debugFP, "Sending premove:\n");
3707                           UserMoveEvent(premoveFromX, premoveFromY,
3708                                         premoveToX, premoveToY,
3709                                         premovePromoChar);
3710                       }
3711                     }
3712
3713                     /* Usually suppress following prompt */
3714                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3715                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3716                         if (looking_at(buf, &i, "*% ")) {
3717                             savingComment = FALSE;
3718                             suppressKibitz = 0;
3719                         }
3720                     }
3721                     next_out = i;
3722                 } else if (started == STARTED_HOLDINGS) {
3723                     int gamenum;
3724                     char new_piece[MSG_SIZ];
3725                     started = STARTED_NONE;
3726                     parse[parse_pos] = NULLCHAR;
3727                     if (appData.debugMode)
3728                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3729                                                         parse, currentMove);
3730                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3731                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3732                         if (gameInfo.variant == VariantNormal) {
3733                           /* [HGM] We seem to switch variant during a game!
3734                            * Presumably no holdings were displayed, so we have
3735                            * to move the position two files to the right to
3736                            * create room for them!
3737                            */
3738                           VariantClass newVariant;
3739                           switch(gameInfo.boardWidth) { // base guess on board width
3740                                 case 9:  newVariant = VariantShogi; break;
3741                                 case 10: newVariant = VariantGreat; break;
3742                                 default: newVariant = VariantCrazyhouse; break;
3743                           }
3744                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3745                           /* Get a move list just to see the header, which
3746                              will tell us whether this is really bug or zh */
3747                           if (ics_getting_history == H_FALSE) {
3748                             ics_getting_history = H_REQUESTED;
3749                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3750                             SendToICS(str);
3751                           }
3752                         }
3753                         new_piece[0] = NULLCHAR;
3754                         sscanf(parse, "game %d white [%s black [%s <- %s",
3755                                &gamenum, white_holding, black_holding,
3756                                new_piece);
3757                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3758                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3759                         /* [HGM] copy holdings to board holdings area */
3760                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3761                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3762                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3763 #if ZIPPY
3764                         if (appData.zippyPlay && first.initDone) {
3765                             ZippyHoldings(white_holding, black_holding,
3766                                           new_piece);
3767                         }
3768 #endif /*ZIPPY*/
3769                         if (tinyLayout || smallLayout) {
3770                             char wh[16], bh[16];
3771                             PackHolding(wh, white_holding);
3772                             PackHolding(bh, black_holding);
3773                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3774                                     gameInfo.white, gameInfo.black);
3775                         } else {
3776                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3777                                     gameInfo.white, white_holding,
3778                                     gameInfo.black, black_holding);
3779                         }
3780                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3781                         DrawPosition(FALSE, boards[currentMove]);
3782                         DisplayTitle(str);
3783                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3784                         sscanf(parse, "game %d white [%s black [%s <- %s",
3785                                &gamenum, white_holding, black_holding,
3786                                new_piece);
3787                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3788                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3789                         /* [HGM] copy holdings to partner-board holdings area */
3790                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3791                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3792                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3793                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3794                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3795                       }
3796                     }
3797                     /* Suppress following prompt */
3798                     if (looking_at(buf, &i, "*% ")) {
3799                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3800                         savingComment = FALSE;
3801                         suppressKibitz = 0;
3802                     }
3803                     next_out = i;
3804                 }
3805                 continue;
3806             }
3807
3808             i++;                /* skip unparsed character and loop back */
3809         }
3810
3811         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3812 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3813 //          SendToPlayer(&buf[next_out], i - next_out);
3814             started != STARTED_HOLDINGS && leftover_start > next_out) {
3815             SendToPlayer(&buf[next_out], leftover_start - next_out);
3816             next_out = i;
3817         }
3818
3819         leftover_len = buf_len - leftover_start;
3820         /* if buffer ends with something we couldn't parse,
3821            reparse it after appending the next read */
3822
3823     } else if (count == 0) {
3824         RemoveInputSource(isr);
3825         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3826     } else {
3827         DisplayFatalError(_("Error reading from ICS"), error, 1);
3828     }
3829 }
3830
3831
3832 /* Board style 12 looks like this:
3833
3834    <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
3835
3836  * The "<12> " is stripped before it gets to this routine.  The two
3837  * trailing 0's (flip state and clock ticking) are later addition, and
3838  * some chess servers may not have them, or may have only the first.
3839  * Additional trailing fields may be added in the future.
3840  */
3841
3842 #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"
3843
3844 #define RELATION_OBSERVING_PLAYED    0
3845 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3846 #define RELATION_PLAYING_MYMOVE      1
3847 #define RELATION_PLAYING_NOTMYMOVE  -1
3848 #define RELATION_EXAMINING           2
3849 #define RELATION_ISOLATED_BOARD     -3
3850 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3851
3852 void
3853 ParseBoard12(string)
3854      char *string;
3855 {
3856     GameMode newGameMode;
3857     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3858     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3859     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3860     char to_play, board_chars[200];
3861     char move_str[500], str[500], elapsed_time[500];
3862     char black[32], white[32];
3863     Board board;
3864     int prevMove = currentMove;
3865     int ticking = 2;
3866     ChessMove moveType;
3867     int fromX, fromY, toX, toY;
3868     char promoChar;
3869     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3870     char *bookHit = NULL; // [HGM] book
3871     Boolean weird = FALSE, reqFlag = FALSE;
3872
3873     fromX = fromY = toX = toY = -1;
3874
3875     newGame = FALSE;
3876
3877     if (appData.debugMode)
3878       fprintf(debugFP, _("Parsing board: %s\n"), string);
3879
3880     move_str[0] = NULLCHAR;
3881     elapsed_time[0] = NULLCHAR;
3882     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3883         int  i = 0, j;
3884         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3885             if(string[i] == ' ') { ranks++; files = 0; }
3886             else files++;
3887             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3888             i++;
3889         }
3890         for(j = 0; j <i; j++) board_chars[j] = string[j];
3891         board_chars[i] = '\0';
3892         string += i + 1;
3893     }
3894     n = sscanf(string, PATTERN, &to_play, &double_push,
3895                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3896                &gamenum, white, black, &relation, &basetime, &increment,
3897                &white_stren, &black_stren, &white_time, &black_time,
3898                &moveNum, str, elapsed_time, move_str, &ics_flip,
3899                &ticking);
3900
3901     if (n < 21) {
3902         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3903         DisplayError(str, 0);
3904         return;
3905     }
3906
3907     /* Convert the move number to internal form */
3908     moveNum = (moveNum - 1) * 2;
3909     if (to_play == 'B') moveNum++;
3910     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3911       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3912                         0, 1);
3913       return;
3914     }
3915
3916     switch (relation) {
3917       case RELATION_OBSERVING_PLAYED:
3918       case RELATION_OBSERVING_STATIC:
3919         if (gamenum == -1) {
3920             /* Old ICC buglet */
3921             relation = RELATION_OBSERVING_STATIC;
3922         }
3923         newGameMode = IcsObserving;
3924         break;
3925       case RELATION_PLAYING_MYMOVE:
3926       case RELATION_PLAYING_NOTMYMOVE:
3927         newGameMode =
3928           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3929             IcsPlayingWhite : IcsPlayingBlack;
3930         break;
3931       case RELATION_EXAMINING:
3932         newGameMode = IcsExamining;
3933         break;
3934       case RELATION_ISOLATED_BOARD:
3935       default:
3936         /* Just display this board.  If user was doing something else,
3937            we will forget about it until the next board comes. */
3938         newGameMode = IcsIdle;
3939         break;
3940       case RELATION_STARTING_POSITION:
3941         newGameMode = gameMode;
3942         break;
3943     }
3944
3945     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3946          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3947       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3948       char *toSqr;
3949       for (k = 0; k < ranks; k++) {
3950         for (j = 0; j < files; j++)
3951           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3952         if(gameInfo.holdingsWidth > 1) {
3953              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3954              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3955         }
3956       }
3957       CopyBoard(partnerBoard, board);
3958       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3959         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3960         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3961       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3962       if(toSqr = strchr(str, '-')) {
3963         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3964         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3965       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3966       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3967       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3968       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3969       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3970       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3971                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3972       DisplayMessage(partnerStatus, "");
3973         partnerBoardValid = TRUE;
3974       return;
3975     }
3976
3977     /* Modify behavior for initial board display on move listing
3978        of wild games.
3979        */
3980     switch (ics_getting_history) {
3981       case H_FALSE:
3982       case H_REQUESTED:
3983         break;
3984       case H_GOT_REQ_HEADER:
3985       case H_GOT_UNREQ_HEADER:
3986         /* This is the initial position of the current game */
3987         gamenum = ics_gamenum;
3988         moveNum = 0;            /* old ICS bug workaround */
3989         if (to_play == 'B') {
3990           startedFromSetupPosition = TRUE;
3991           blackPlaysFirst = TRUE;
3992           moveNum = 1;
3993           if (forwardMostMove == 0) forwardMostMove = 1;
3994           if (backwardMostMove == 0) backwardMostMove = 1;
3995           if (currentMove == 0) currentMove = 1;
3996         }
3997         newGameMode = gameMode;
3998         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3999         break;
4000       case H_GOT_UNWANTED_HEADER:
4001         /* This is an initial board that we don't want */
4002         return;
4003       case H_GETTING_MOVES:
4004         /* Should not happen */
4005         DisplayError(_("Error gathering move list: extra board"), 0);
4006         ics_getting_history = H_FALSE;
4007         return;
4008     }
4009
4010    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4011                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4012      /* [HGM] We seem to have switched variant unexpectedly
4013       * Try to guess new variant from board size
4014       */
4015           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4016           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4017           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4018           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4019           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4020           if(!weird) newVariant = VariantNormal;
4021           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4022           /* Get a move list just to see the header, which
4023              will tell us whether this is really bug or zh */
4024           if (ics_getting_history == H_FALSE) {
4025             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4026             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4027             SendToICS(str);
4028           }
4029     }
4030
4031     /* Take action if this is the first board of a new game, or of a
4032        different game than is currently being displayed.  */
4033     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4034         relation == RELATION_ISOLATED_BOARD) {
4035
4036         /* Forget the old game and get the history (if any) of the new one */
4037         if (gameMode != BeginningOfGame) {
4038           Reset(TRUE, TRUE);
4039         }
4040         newGame = TRUE;
4041         if (appData.autoRaiseBoard) BoardToTop();
4042         prevMove = -3;
4043         if (gamenum == -1) {
4044             newGameMode = IcsIdle;
4045         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4046                    appData.getMoveList && !reqFlag) {
4047             /* Need to get game history */
4048             ics_getting_history = H_REQUESTED;
4049             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4050             SendToICS(str);
4051         }
4052
4053         /* Initially flip the board to have black on the bottom if playing
4054            black or if the ICS flip flag is set, but let the user change
4055            it with the Flip View button. */
4056         flipView = appData.autoFlipView ?
4057           (newGameMode == IcsPlayingBlack) || ics_flip :
4058           appData.flipView;
4059
4060         /* Done with values from previous mode; copy in new ones */
4061         gameMode = newGameMode;
4062         ModeHighlight();
4063         ics_gamenum = gamenum;
4064         if (gamenum == gs_gamenum) {
4065             int klen = strlen(gs_kind);
4066             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4067             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4068             gameInfo.event = StrSave(str);
4069         } else {
4070             gameInfo.event = StrSave("ICS game");
4071         }
4072         gameInfo.site = StrSave(appData.icsHost);
4073         gameInfo.date = PGNDate();
4074         gameInfo.round = StrSave("-");
4075         gameInfo.white = StrSave(white);
4076         gameInfo.black = StrSave(black);
4077         timeControl = basetime * 60 * 1000;
4078         timeControl_2 = 0;
4079         timeIncrement = increment * 1000;
4080         movesPerSession = 0;
4081         gameInfo.timeControl = TimeControlTagValue();
4082         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4083   if (appData.debugMode) {
4084     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4085     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4086     setbuf(debugFP, NULL);
4087   }
4088
4089         gameInfo.outOfBook = NULL;
4090
4091         /* Do we have the ratings? */
4092         if (strcmp(player1Name, white) == 0 &&
4093             strcmp(player2Name, black) == 0) {
4094             if (appData.debugMode)
4095               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4096                       player1Rating, player2Rating);
4097             gameInfo.whiteRating = player1Rating;
4098             gameInfo.blackRating = player2Rating;
4099         } else if (strcmp(player2Name, white) == 0 &&
4100                    strcmp(player1Name, black) == 0) {
4101             if (appData.debugMode)
4102               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4103                       player2Rating, player1Rating);
4104             gameInfo.whiteRating = player2Rating;
4105             gameInfo.blackRating = player1Rating;
4106         }
4107         player1Name[0] = player2Name[0] = NULLCHAR;
4108
4109         /* Silence shouts if requested */
4110         if (appData.quietPlay &&
4111             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4112             SendToICS(ics_prefix);
4113             SendToICS("set shout 0\n");
4114         }
4115     }
4116
4117     /* Deal with midgame name changes */
4118     if (!newGame) {
4119         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4120             if (gameInfo.white) free(gameInfo.white);
4121             gameInfo.white = StrSave(white);
4122         }
4123         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4124             if (gameInfo.black) free(gameInfo.black);
4125             gameInfo.black = StrSave(black);
4126         }
4127     }
4128
4129     /* Throw away game result if anything actually changes in examine mode */
4130     if (gameMode == IcsExamining && !newGame) {
4131         gameInfo.result = GameUnfinished;
4132         if (gameInfo.resultDetails != NULL) {
4133             free(gameInfo.resultDetails);
4134             gameInfo.resultDetails = NULL;
4135         }
4136     }
4137
4138     /* In pausing && IcsExamining mode, we ignore boards coming
4139        in if they are in a different variation than we are. */
4140     if (pauseExamInvalid) return;
4141     if (pausing && gameMode == IcsExamining) {
4142         if (moveNum <= pauseExamForwardMostMove) {
4143             pauseExamInvalid = TRUE;
4144             forwardMostMove = pauseExamForwardMostMove;
4145             return;
4146         }
4147     }
4148
4149   if (appData.debugMode) {
4150     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4151   }
4152     /* Parse the board */
4153     for (k = 0; k < ranks; k++) {
4154       for (j = 0; j < files; j++)
4155         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4156       if(gameInfo.holdingsWidth > 1) {
4157            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4158            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4159       }
4160     }
4161     CopyBoard(boards[moveNum], board);
4162     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4163     if (moveNum == 0) {
4164         startedFromSetupPosition =
4165           !CompareBoards(board, initialPosition);
4166         if(startedFromSetupPosition)
4167             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4168     }
4169
4170     /* [HGM] Set castling rights. Take the outermost Rooks,
4171        to make it also work for FRC opening positions. Note that board12
4172        is really defective for later FRC positions, as it has no way to
4173        indicate which Rook can castle if they are on the same side of King.
4174        For the initial position we grant rights to the outermost Rooks,
4175        and remember thos rights, and we then copy them on positions
4176        later in an FRC game. This means WB might not recognize castlings with
4177        Rooks that have moved back to their original position as illegal,
4178        but in ICS mode that is not its job anyway.
4179     */
4180     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4181     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4182
4183         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4184             if(board[0][i] == WhiteRook) j = i;
4185         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4186         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4187             if(board[0][i] == WhiteRook) j = i;
4188         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4189         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4190             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4191         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4192         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4193             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4194         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4195
4196         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4197         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4198             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4199         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4200             if(board[BOARD_HEIGHT-1][k] == bKing)
4201                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4202         if(gameInfo.variant == VariantTwoKings) {
4203             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4204             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4205             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4206         }
4207     } else { int r;
4208         r = boards[moveNum][CASTLING][0] = initialRights[0];
4209         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4210         r = boards[moveNum][CASTLING][1] = initialRights[1];
4211         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4212         r = boards[moveNum][CASTLING][3] = initialRights[3];
4213         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4214         r = boards[moveNum][CASTLING][4] = initialRights[4];
4215         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4216         /* wildcastle kludge: always assume King has rights */
4217         r = boards[moveNum][CASTLING][2] = initialRights[2];
4218         r = boards[moveNum][CASTLING][5] = initialRights[5];
4219     }
4220     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4221     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4222
4223
4224     if (ics_getting_history == H_GOT_REQ_HEADER ||
4225         ics_getting_history == H_GOT_UNREQ_HEADER) {
4226         /* This was an initial position from a move list, not
4227            the current position */
4228         return;
4229     }
4230
4231     /* Update currentMove and known move number limits */
4232     newMove = newGame || moveNum > forwardMostMove;
4233
4234     if (newGame) {
4235         forwardMostMove = backwardMostMove = currentMove = moveNum;
4236         if (gameMode == IcsExamining && moveNum == 0) {
4237           /* Workaround for ICS limitation: we are not told the wild
4238              type when starting to examine a game.  But if we ask for
4239              the move list, the move list header will tell us */
4240             ics_getting_history = H_REQUESTED;
4241             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4242             SendToICS(str);
4243         }
4244     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4245                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4246 #if ZIPPY
4247         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4248         /* [HGM] applied this also to an engine that is silently watching        */
4249         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4250             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4251             gameInfo.variant == currentlyInitializedVariant) {
4252           takeback = forwardMostMove - moveNum;
4253           for (i = 0; i < takeback; i++) {
4254             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4255             SendToProgram("undo\n", &first);
4256           }
4257         }
4258 #endif
4259
4260         forwardMostMove = moveNum;
4261         if (!pausing || currentMove > forwardMostMove)
4262           currentMove = forwardMostMove;
4263     } else {
4264         /* New part of history that is not contiguous with old part */
4265         if (pausing && gameMode == IcsExamining) {
4266             pauseExamInvalid = TRUE;
4267             forwardMostMove = pauseExamForwardMostMove;
4268             return;
4269         }
4270         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4271 #if ZIPPY
4272             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4273                 // [HGM] when we will receive the move list we now request, it will be
4274                 // fed to the engine from the first move on. So if the engine is not
4275                 // in the initial position now, bring it there.
4276                 InitChessProgram(&first, 0);
4277             }
4278 #endif
4279             ics_getting_history = H_REQUESTED;
4280             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4281             SendToICS(str);
4282         }
4283         forwardMostMove = backwardMostMove = currentMove = moveNum;
4284     }
4285
4286     /* Update the clocks */
4287     if (strchr(elapsed_time, '.')) {
4288       /* Time is in ms */
4289       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4290       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4291     } else {
4292       /* Time is in seconds */
4293       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4294       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4295     }
4296
4297
4298 #if ZIPPY
4299     if (appData.zippyPlay && newGame &&
4300         gameMode != IcsObserving && gameMode != IcsIdle &&
4301         gameMode != IcsExamining)
4302       ZippyFirstBoard(moveNum, basetime, increment);
4303 #endif
4304
4305     /* Put the move on the move list, first converting
4306        to canonical algebraic form. */
4307     if (moveNum > 0) {
4308   if (appData.debugMode) {
4309     if (appData.debugMode) { int f = forwardMostMove;
4310         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4311                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4312                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4313     }
4314     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4315     fprintf(debugFP, "moveNum = %d\n", moveNum);
4316     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4317     setbuf(debugFP, NULL);
4318   }
4319         if (moveNum <= backwardMostMove) {
4320             /* We don't know what the board looked like before
4321                this move.  Punt. */
4322           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4323             strcat(parseList[moveNum - 1], " ");
4324             strcat(parseList[moveNum - 1], elapsed_time);
4325             moveList[moveNum - 1][0] = NULLCHAR;
4326         } else if (strcmp(move_str, "none") == 0) {
4327             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4328             /* Again, we don't know what the board looked like;
4329                this is really the start of the game. */
4330             parseList[moveNum - 1][0] = NULLCHAR;
4331             moveList[moveNum - 1][0] = NULLCHAR;
4332             backwardMostMove = moveNum;
4333             startedFromSetupPosition = TRUE;
4334             fromX = fromY = toX = toY = -1;
4335         } else {
4336           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4337           //                 So we parse the long-algebraic move string in stead of the SAN move
4338           int valid; char buf[MSG_SIZ], *prom;
4339
4340           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4341                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4342           // str looks something like "Q/a1-a2"; kill the slash
4343           if(str[1] == '/')
4344             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4345           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4346           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4347                 strcat(buf, prom); // long move lacks promo specification!
4348           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4349                 if(appData.debugMode)
4350                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4351                 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4352           }
4353           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4354                                 &fromX, &fromY, &toX, &toY, &promoChar)
4355                || ParseOneMove(buf, moveNum - 1, &moveType,
4356                                 &fromX, &fromY, &toX, &toY, &promoChar);
4357           // end of long SAN patch
4358           if (valid) {
4359             (void) CoordsToAlgebraic(boards[moveNum - 1],
4360                                      PosFlags(moveNum - 1),
4361                                      fromY, fromX, toY, toX, promoChar,
4362                                      parseList[moveNum-1]);
4363             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4364               case MT_NONE:
4365               case MT_STALEMATE:
4366               default:
4367                 break;
4368               case MT_CHECK:
4369                 if(gameInfo.variant != VariantShogi)
4370                     strcat(parseList[moveNum - 1], "+");
4371                 break;
4372               case MT_CHECKMATE:
4373               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4374                 strcat(parseList[moveNum - 1], "#");
4375                 break;
4376             }
4377             strcat(parseList[moveNum - 1], " ");
4378             strcat(parseList[moveNum - 1], elapsed_time);
4379             /* currentMoveString is set as a side-effect of ParseOneMove */
4380             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4381             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4382             strcat(moveList[moveNum - 1], "\n");
4383
4384             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4385               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4386                 ChessSquare old, new = boards[moveNum][k][j];
4387                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4388                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4389                   if(old == new) continue;
4390                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4391                   else if(new == WhiteWazir || new == BlackWazir) {
4392                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4393                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4394                       else boards[moveNum][k][j] = old; // preserve type of Gold
4395                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4396                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4397               }
4398           } else {
4399             /* Move from ICS was illegal!?  Punt. */
4400             if (appData.debugMode) {
4401               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4402               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4403             }
4404             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4405             strcat(parseList[moveNum - 1], " ");
4406             strcat(parseList[moveNum - 1], elapsed_time);
4407             moveList[moveNum - 1][0] = NULLCHAR;
4408             fromX = fromY = toX = toY = -1;
4409           }
4410         }
4411   if (appData.debugMode) {
4412     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4413     setbuf(debugFP, NULL);
4414   }
4415
4416 #if ZIPPY
4417         /* Send move to chess program (BEFORE animating it). */
4418         if (appData.zippyPlay && !newGame && newMove &&
4419            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4420
4421             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4422                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4423                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4424                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4425                             move_str);
4426                     DisplayError(str, 0);
4427                 } else {
4428                     if (first.sendTime) {
4429                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4430                     }
4431                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4432                     if (firstMove && !bookHit) {
4433                         firstMove = FALSE;
4434                         if (first.useColors) {
4435                           SendToProgram(gameMode == IcsPlayingWhite ?
4436                                         "white\ngo\n" :
4437                                         "black\ngo\n", &first);
4438                         } else {
4439                           SendToProgram("go\n", &first);
4440                         }
4441                         first.maybeThinking = TRUE;
4442                     }
4443                 }
4444             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4445               if (moveList[moveNum - 1][0] == NULLCHAR) {
4446                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4447                 DisplayError(str, 0);
4448               } else {
4449                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4450                 SendMoveToProgram(moveNum - 1, &first);
4451               }
4452             }
4453         }
4454 #endif
4455     }
4456
4457     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4458         /* If move comes from a remote source, animate it.  If it
4459            isn't remote, it will have already been animated. */
4460         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4461             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4462         }
4463         if (!pausing && appData.highlightLastMove) {
4464             SetHighlights(fromX, fromY, toX, toY);
4465         }
4466     }
4467
4468     /* Start the clocks */
4469     whiteFlag = blackFlag = FALSE;
4470     appData.clockMode = !(basetime == 0 && increment == 0);
4471     if (ticking == 0) {
4472       ics_clock_paused = TRUE;
4473       StopClocks();
4474     } else if (ticking == 1) {
4475       ics_clock_paused = FALSE;
4476     }
4477     if (gameMode == IcsIdle ||
4478         relation == RELATION_OBSERVING_STATIC ||
4479         relation == RELATION_EXAMINING ||
4480         ics_clock_paused)
4481       DisplayBothClocks();
4482     else
4483       StartClocks();
4484
4485     /* Display opponents and material strengths */
4486     if (gameInfo.variant != VariantBughouse &&
4487         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4488         if (tinyLayout || smallLayout) {
4489             if(gameInfo.variant == VariantNormal)
4490               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment);
4493             else
4494               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4495                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4496                     basetime, increment, (int) gameInfo.variant);
4497         } else {
4498             if(gameInfo.variant == VariantNormal)
4499               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4500                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4501                     basetime, increment);
4502             else
4503               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4504                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4505                     basetime, increment, VariantName(gameInfo.variant));
4506         }
4507         DisplayTitle(str);
4508   if (appData.debugMode) {
4509     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4510   }
4511     }
4512
4513
4514     /* Display the board */
4515     if (!pausing && !appData.noGUI) {
4516
4517       if (appData.premove)
4518           if (!gotPremove ||
4519              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4520              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4521               ClearPremoveHighlights();
4522
4523       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4524         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4525       DrawPosition(j, boards[currentMove]);
4526
4527       DisplayMove(moveNum - 1);
4528       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4529             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4530               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4531         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4532       }
4533     }
4534
4535     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4536 #if ZIPPY
4537     if(bookHit) { // [HGM] book: simulate book reply
4538         static char bookMove[MSG_SIZ]; // a bit generous?
4539
4540         programStats.nodes = programStats.depth = programStats.time =
4541         programStats.score = programStats.got_only_move = 0;
4542         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4543
4544         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4545         strcat(bookMove, bookHit);
4546         HandleMachineMove(bookMove, &first);
4547     }
4548 #endif
4549 }
4550
4551 void
4552 GetMoveListEvent()
4553 {
4554     char buf[MSG_SIZ];
4555     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4556         ics_getting_history = H_REQUESTED;
4557         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4558         SendToICS(buf);
4559     }
4560 }
4561
4562 void
4563 AnalysisPeriodicEvent(force)
4564      int force;
4565 {
4566     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4567          && !force) || !appData.periodicUpdates)
4568       return;
4569
4570     /* Send . command to Crafty to collect stats */
4571     SendToProgram(".\n", &first);
4572
4573     /* Don't send another until we get a response (this makes
4574        us stop sending to old Crafty's which don't understand
4575        the "." command (sending illegal cmds resets node count & time,
4576        which looks bad)) */
4577     programStats.ok_to_send = 0;
4578 }
4579
4580 void ics_update_width(new_width)
4581         int new_width;
4582 {
4583         ics_printf("set width %d\n", new_width);
4584 }
4585
4586 void
4587 SendMoveToProgram(moveNum, cps)
4588      int moveNum;
4589      ChessProgramState *cps;
4590 {
4591     char buf[MSG_SIZ];
4592
4593     if (cps->useUsermove) {
4594       SendToProgram("usermove ", cps);
4595     }
4596     if (cps->useSAN) {
4597       char *space;
4598       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4599         int len = space - parseList[moveNum];
4600         memcpy(buf, parseList[moveNum], len);
4601         buf[len++] = '\n';
4602         buf[len] = NULLCHAR;
4603       } else {
4604         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4605       }
4606       SendToProgram(buf, cps);
4607     } else {
4608       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4609         AlphaRank(moveList[moveNum], 4);
4610         SendToProgram(moveList[moveNum], cps);
4611         AlphaRank(moveList[moveNum], 4); // and back
4612       } else
4613       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4614        * the engine. It would be nice to have a better way to identify castle
4615        * moves here. */
4616       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4617                                                                          && cps->useOOCastle) {
4618         int fromX = moveList[moveNum][0] - AAA;
4619         int fromY = moveList[moveNum][1] - ONE;
4620         int toX = moveList[moveNum][2] - AAA;
4621         int toY = moveList[moveNum][3] - ONE;
4622         if((boards[moveNum][fromY][fromX] == WhiteKing
4623             && boards[moveNum][toY][toX] == WhiteRook)
4624            || (boards[moveNum][fromY][fromX] == BlackKing
4625                && boards[moveNum][toY][toX] == BlackRook)) {
4626           if(toX > fromX) SendToProgram("O-O\n", cps);
4627           else SendToProgram("O-O-O\n", cps);
4628         }
4629         else SendToProgram(moveList[moveNum], cps);
4630       }
4631       else SendToProgram(moveList[moveNum], cps);
4632       /* End of additions by Tord */
4633     }
4634
4635     /* [HGM] setting up the opening has brought engine in force mode! */
4636     /*       Send 'go' if we are in a mode where machine should play. */
4637     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4638         (gameMode == TwoMachinesPlay   ||
4639 #if ZIPPY
4640          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4641 #endif
4642          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4643         SendToProgram("go\n", cps);
4644   if (appData.debugMode) {
4645     fprintf(debugFP, "(extra)\n");
4646   }
4647     }
4648     setboardSpoiledMachineBlack = 0;
4649 }
4650
4651 void
4652 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4653      ChessMove moveType;
4654      int fromX, fromY, toX, toY;
4655      char promoChar;
4656 {
4657     char user_move[MSG_SIZ];
4658
4659     switch (moveType) {
4660       default:
4661         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4662                 (int)moveType, fromX, fromY, toX, toY);
4663         DisplayError(user_move + strlen("say "), 0);
4664         break;
4665       case WhiteKingSideCastle:
4666       case BlackKingSideCastle:
4667       case WhiteQueenSideCastleWild:
4668       case BlackQueenSideCastleWild:
4669       /* PUSH Fabien */
4670       case WhiteHSideCastleFR:
4671       case BlackHSideCastleFR:
4672       /* POP Fabien */
4673         snprintf(user_move, MSG_SIZ, "o-o\n");
4674         break;
4675       case WhiteQueenSideCastle:
4676       case BlackQueenSideCastle:
4677       case WhiteKingSideCastleWild:
4678       case BlackKingSideCastleWild:
4679       /* PUSH Fabien */
4680       case WhiteASideCastleFR:
4681       case BlackASideCastleFR:
4682       /* POP Fabien */
4683         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4684         break;
4685       case WhiteNonPromotion:
4686       case BlackNonPromotion:
4687         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4688         break;
4689       case WhitePromotion:
4690       case BlackPromotion:
4691         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4692           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4693                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4694                 PieceToChar(WhiteFerz));
4695         else if(gameInfo.variant == VariantGreat)
4696           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4697                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4698                 PieceToChar(WhiteMan));
4699         else
4700           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4701                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4702                 promoChar);
4703         break;
4704       case WhiteDrop:
4705       case BlackDrop:
4706       drop:
4707         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4708                  ToUpper(PieceToChar((ChessSquare) fromX)),
4709                  AAA + toX, ONE + toY);
4710         break;
4711       case IllegalMove:  /* could be a variant we don't quite understand */
4712         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4713       case NormalMove:
4714       case WhiteCapturesEnPassant:
4715       case BlackCapturesEnPassant:
4716         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4717                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4718         break;
4719     }
4720     SendToICS(user_move);
4721     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4722         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4723 }
4724
4725 void
4726 UploadGameEvent()
4727 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4728     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4729     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4730     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4731         DisplayError("You cannot do this while you are playing or observing", 0);
4732         return;
4733     }
4734     if(gameMode != IcsExamining) { // is this ever not the case?
4735         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4736
4737         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4738           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4739         } else { // on FICS we must first go to general examine mode
4740           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4741         }
4742         if(gameInfo.variant != VariantNormal) {
4743             // try figure out wild number, as xboard names are not always valid on ICS
4744             for(i=1; i<=36; i++) {
4745               snprintf(buf, MSG_SIZ, "wild/%d", i);
4746                 if(StringToVariant(buf) == gameInfo.variant) break;
4747             }
4748             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4749             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4750             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4751         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4752         SendToICS(ics_prefix);
4753         SendToICS(buf);
4754         if(startedFromSetupPosition || backwardMostMove != 0) {
4755           fen = PositionToFEN(backwardMostMove, NULL);
4756           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4757             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4758             SendToICS(buf);
4759           } else { // FICS: everything has to set by separate bsetup commands
4760             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4761             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4762             SendToICS(buf);
4763             if(!WhiteOnMove(backwardMostMove)) {
4764                 SendToICS("bsetup tomove black\n");
4765             }
4766             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4767             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4768             SendToICS(buf);
4769             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4770             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4771             SendToICS(buf);
4772             i = boards[backwardMostMove][EP_STATUS];
4773             if(i >= 0) { // set e.p.
4774               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4775                 SendToICS(buf);
4776             }
4777             bsetup++;
4778           }
4779         }
4780       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4781             SendToICS("bsetup done\n"); // switch to normal examining.
4782     }
4783     for(i = backwardMostMove; i<last; i++) {
4784         char buf[20];
4785         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4786         SendToICS(buf);
4787     }
4788     SendToICS(ics_prefix);
4789     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4790 }
4791
4792 void
4793 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4794      int rf, ff, rt, ft;
4795      char promoChar;
4796      char move[7];
4797 {
4798     if (rf == DROP_RANK) {
4799       sprintf(move, "%c@%c%c\n",
4800                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4801     } else {
4802         if (promoChar == 'x' || promoChar == NULLCHAR) {
4803           sprintf(move, "%c%c%c%c\n",
4804                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4805         } else {
4806             sprintf(move, "%c%c%c%c%c\n",
4807                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4808         }
4809     }
4810 }
4811
4812 void
4813 ProcessICSInitScript(f)
4814      FILE *f;
4815 {
4816     char buf[MSG_SIZ];
4817
4818     while (fgets(buf, MSG_SIZ, f)) {
4819         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4820     }
4821
4822     fclose(f);
4823 }
4824
4825
4826 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4827 void
4828 AlphaRank(char *move, int n)
4829 {
4830 //    char *p = move, c; int x, y;
4831
4832     if (appData.debugMode) {
4833         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4834     }
4835
4836     if(move[1]=='*' &&
4837        move[2]>='0' && move[2]<='9' &&
4838        move[3]>='a' && move[3]<='x'    ) {
4839         move[1] = '@';
4840         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4841         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4842     } else
4843     if(move[0]>='0' && move[0]<='9' &&
4844        move[1]>='a' && move[1]<='x' &&
4845        move[2]>='0' && move[2]<='9' &&
4846        move[3]>='a' && move[3]<='x'    ) {
4847         /* input move, Shogi -> normal */
4848         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4849         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4850         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4851         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4852     } else
4853     if(move[1]=='@' &&
4854        move[3]>='0' && move[3]<='9' &&
4855        move[2]>='a' && move[2]<='x'    ) {
4856         move[1] = '*';
4857         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4858         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4859     } else
4860     if(
4861        move[0]>='a' && move[0]<='x' &&
4862        move[3]>='0' && move[3]<='9' &&
4863        move[2]>='a' && move[2]<='x'    ) {
4864          /* output move, normal -> Shogi */
4865         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4866         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4867         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4868         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4869         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4870     }
4871     if (appData.debugMode) {
4872         fprintf(debugFP, "   out = '%s'\n", move);
4873     }
4874 }
4875
4876 char yy_textstr[8000];
4877
4878 /* Parser for moves from gnuchess, ICS, or user typein box */
4879 Boolean
4880 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4881      char *move;
4882      int moveNum;
4883      ChessMove *moveType;
4884      int *fromX, *fromY, *toX, *toY;
4885      char *promoChar;
4886 {
4887     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4888
4889     switch (*moveType) {
4890       case WhitePromotion:
4891       case BlackPromotion:
4892       case WhiteNonPromotion:
4893       case BlackNonPromotion:
4894       case NormalMove:
4895       case WhiteCapturesEnPassant:
4896       case BlackCapturesEnPassant:
4897       case WhiteKingSideCastle:
4898       case WhiteQueenSideCastle:
4899       case BlackKingSideCastle:
4900       case BlackQueenSideCastle:
4901       case WhiteKingSideCastleWild:
4902       case WhiteQueenSideCastleWild:
4903       case BlackKingSideCastleWild:
4904       case BlackQueenSideCastleWild:
4905       /* Code added by Tord: */
4906       case WhiteHSideCastleFR:
4907       case WhiteASideCastleFR:
4908       case BlackHSideCastleFR:
4909       case BlackASideCastleFR:
4910       /* End of code added by Tord */
4911       case IllegalMove:         /* bug or odd chess variant */
4912         *fromX = currentMoveString[0] - AAA;
4913         *fromY = currentMoveString[1] - ONE;
4914         *toX = currentMoveString[2] - AAA;
4915         *toY = currentMoveString[3] - ONE;
4916         *promoChar = currentMoveString[4];
4917         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4918             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4919     if (appData.debugMode) {
4920         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4921     }
4922             *fromX = *fromY = *toX = *toY = 0;
4923             return FALSE;
4924         }
4925         if (appData.testLegality) {
4926           return (*moveType != IllegalMove);
4927         } else {
4928           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4929                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4930         }
4931
4932       case WhiteDrop:
4933       case BlackDrop:
4934         *fromX = *moveType == WhiteDrop ?
4935           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4936           (int) CharToPiece(ToLower(currentMoveString[0]));
4937         *fromY = DROP_RANK;
4938         *toX = currentMoveString[2] - AAA;
4939         *toY = currentMoveString[3] - ONE;
4940         *promoChar = NULLCHAR;
4941         return TRUE;
4942
4943       case AmbiguousMove:
4944       case ImpossibleMove:
4945       case EndOfFile:
4946       case ElapsedTime:
4947       case Comment:
4948       case PGNTag:
4949       case NAG:
4950       case WhiteWins:
4951       case BlackWins:
4952       case GameIsDrawn:
4953       default:
4954     if (appData.debugMode) {
4955         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4956     }
4957         /* bug? */
4958         *fromX = *fromY = *toX = *toY = 0;
4959         *promoChar = NULLCHAR;
4960         return FALSE;
4961     }
4962 }
4963
4964
4965 void
4966 ParsePV(char *pv, Boolean storeComments)
4967 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4968   int fromX, fromY, toX, toY; char promoChar;
4969   ChessMove moveType;
4970   Boolean valid;
4971   int nr = 0;
4972
4973   endPV = forwardMostMove;
4974   do {
4975     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4976     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4977     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4978 if(appData.debugMode){
4979 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);
4980 }
4981     if(!valid && nr == 0 &&
4982        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4983         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4984         // Hande case where played move is different from leading PV move
4985         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4986         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4987         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4988         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4989           endPV += 2; // if position different, keep this
4990           moveList[endPV-1][0] = fromX + AAA;
4991           moveList[endPV-1][1] = fromY + ONE;
4992           moveList[endPV-1][2] = toX + AAA;
4993           moveList[endPV-1][3] = toY + ONE;
4994           parseList[endPV-1][0] = NULLCHAR;
4995           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4996         }
4997       }
4998     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4999     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5000     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5001     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5002         valid++; // allow comments in PV
5003         continue;
5004     }
5005     nr++;
5006     if(endPV+1 > framePtr) break; // no space, truncate
5007     if(!valid) break;
5008     endPV++;
5009     CopyBoard(boards[endPV], boards[endPV-1]);
5010     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5011     moveList[endPV-1][0] = fromX + AAA;
5012     moveList[endPV-1][1] = fromY + ONE;
5013     moveList[endPV-1][2] = toX + AAA;
5014     moveList[endPV-1][3] = toY + ONE;
5015     if(storeComments)
5016         CoordsToAlgebraic(boards[endPV - 1],
5017                              PosFlags(endPV - 1),
5018                              fromY, fromX, toY, toX, promoChar,
5019                              parseList[endPV - 1]);
5020     else
5021         parseList[endPV-1][0] = NULLCHAR;
5022   } while(valid);
5023   currentMove = endPV;
5024   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5025   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5026                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5027   DrawPosition(TRUE, boards[currentMove]);
5028 }
5029
5030 static int lastX, lastY;
5031
5032 Boolean
5033 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5034 {
5035         int startPV;
5036         char *p;
5037
5038         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5039         lastX = x; lastY = y;
5040         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5041         startPV = index;
5042         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5043         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5044         index = startPV;
5045         do{ while(buf[index] && buf[index] != '\n') index++;
5046         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5047         buf[index] = 0;
5048         ParsePV(buf+startPV, FALSE);
5049         *start = startPV; *end = index-1;
5050         return TRUE;
5051 }
5052
5053 Boolean
5054 LoadPV(int x, int y)
5055 { // called on right mouse click to load PV
5056   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5057   lastX = x; lastY = y;
5058   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5059   return TRUE;
5060 }
5061
5062 void
5063 UnLoadPV()
5064 {
5065   if(endPV < 0) return;
5066   endPV = -1;
5067   currentMove = forwardMostMove;
5068   ClearPremoveHighlights();
5069   DrawPosition(TRUE, boards[currentMove]);
5070 }
5071
5072 void
5073 MovePV(int x, int y, int h)
5074 { // step through PV based on mouse coordinates (called on mouse move)
5075   int margin = h>>3, step = 0;
5076
5077   if(endPV < 0) return;
5078   // we must somehow check if right button is still down (might be released off board!)
5079   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5080   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5081   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5082   if(!step) return;
5083   lastX = x; lastY = y;
5084   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5085   currentMove += step;
5086   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5087   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5088                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5089   DrawPosition(FALSE, boards[currentMove]);
5090 }
5091
5092
5093 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5094 // All positions will have equal probability, but the current method will not provide a unique
5095 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5096 #define DARK 1
5097 #define LITE 2
5098 #define ANY 3
5099
5100 int squaresLeft[4];
5101 int piecesLeft[(int)BlackPawn];
5102 int seed, nrOfShuffles;
5103
5104 void GetPositionNumber()
5105 {       // sets global variable seed
5106         int i;
5107
5108         seed = appData.defaultFrcPosition;
5109         if(seed < 0) { // randomize based on time for negative FRC position numbers
5110                 for(i=0; i<50; i++) seed += random();
5111                 seed = random() ^ random() >> 8 ^ random() << 8;
5112                 if(seed<0) seed = -seed;
5113         }
5114 }
5115
5116 int put(Board board, int pieceType, int rank, int n, int shade)
5117 // put the piece on the (n-1)-th empty squares of the given shade
5118 {
5119         int i;
5120
5121         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5122                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5123                         board[rank][i] = (ChessSquare) pieceType;
5124                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5125                         squaresLeft[ANY]--;
5126                         piecesLeft[pieceType]--;
5127                         return i;
5128                 }
5129         }
5130         return -1;
5131 }
5132
5133
5134 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5135 // calculate where the next piece goes, (any empty square), and put it there
5136 {
5137         int i;
5138
5139         i = seed % squaresLeft[shade];
5140         nrOfShuffles *= squaresLeft[shade];
5141         seed /= squaresLeft[shade];
5142         put(board, pieceType, rank, i, shade);
5143 }
5144
5145 void AddTwoPieces(Board board, int pieceType, int rank)
5146 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5147 {
5148         int i, n=squaresLeft[ANY], j=n-1, k;
5149
5150         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5151         i = seed % k;  // pick one
5152         nrOfShuffles *= k;
5153         seed /= k;
5154         while(i >= j) i -= j--;
5155         j = n - 1 - j; i += j;
5156         put(board, pieceType, rank, j, ANY);
5157         put(board, pieceType, rank, i, ANY);
5158 }
5159
5160 void SetUpShuffle(Board board, int number)
5161 {
5162         int i, p, first=1;
5163
5164         GetPositionNumber(); nrOfShuffles = 1;
5165
5166         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5167         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5168         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5169
5170         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5171
5172         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5173             p = (int) board[0][i];
5174             if(p < (int) BlackPawn) piecesLeft[p] ++;
5175             board[0][i] = EmptySquare;
5176         }
5177
5178         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5179             // shuffles restricted to allow normal castling put KRR first
5180             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5181                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5182             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5183                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5184             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5185                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5186             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5187                 put(board, WhiteRook, 0, 0, ANY);
5188             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5189         }
5190
5191         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5192             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5193             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5194                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5195                 while(piecesLeft[p] >= 2) {
5196                     AddOnePiece(board, p, 0, LITE);
5197                     AddOnePiece(board, p, 0, DARK);
5198                 }
5199                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5200             }
5201
5202         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5203             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5204             // but we leave King and Rooks for last, to possibly obey FRC restriction
5205             if(p == (int)WhiteRook) continue;
5206             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5207             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5208         }
5209
5210         // now everything is placed, except perhaps King (Unicorn) and Rooks
5211
5212         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5213             // Last King gets castling rights
5214             while(piecesLeft[(int)WhiteUnicorn]) {
5215                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5216                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5217             }
5218
5219             while(piecesLeft[(int)WhiteKing]) {
5220                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5221                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5222             }
5223
5224
5225         } else {
5226             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5227             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5228         }
5229
5230         // Only Rooks can be left; simply place them all
5231         while(piecesLeft[(int)WhiteRook]) {
5232                 i = put(board, WhiteRook, 0, 0, ANY);
5233                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5234                         if(first) {
5235                                 first=0;
5236                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5237                         }
5238                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5239                 }
5240         }
5241         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5242             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5243         }
5244
5245         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5246 }
5247
5248 int SetCharTable( char *table, const char * map )
5249 /* [HGM] moved here from winboard.c because of its general usefulness */
5250 /*       Basically a safe strcpy that uses the last character as King */
5251 {
5252     int result = FALSE; int NrPieces;
5253
5254     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5255                     && NrPieces >= 12 && !(NrPieces&1)) {
5256         int i; /* [HGM] Accept even length from 12 to 34 */
5257
5258         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5259         for( i=0; i<NrPieces/2-1; i++ ) {
5260             table[i] = map[i];
5261             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5262         }
5263         table[(int) WhiteKing]  = map[NrPieces/2-1];
5264         table[(int) BlackKing]  = map[NrPieces-1];
5265
5266         result = TRUE;
5267     }
5268
5269     return result;
5270 }
5271
5272 void Prelude(Board board)
5273 {       // [HGM] superchess: random selection of exo-pieces
5274         int i, j, k; ChessSquare p;
5275         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5276
5277         GetPositionNumber(); // use FRC position number
5278
5279         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5280             SetCharTable(pieceToChar, appData.pieceToCharTable);
5281             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5282                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5283         }
5284
5285         j = seed%4;                 seed /= 4;
5286         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5287         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5288         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5289         j = seed%3 + (seed%3 >= j); seed /= 3;
5290         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5291         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5292         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5293         j = seed%3;                 seed /= 3;
5294         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5295         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5296         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5297         j = seed%2 + (seed%2 >= j); seed /= 2;
5298         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5299         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5300         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5301         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5302         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5303         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5304         put(board, exoPieces[0],    0, 0, ANY);
5305         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5306 }
5307
5308 void
5309 InitPosition(redraw)
5310      int redraw;
5311 {
5312     ChessSquare (* pieces)[BOARD_FILES];
5313     int i, j, pawnRow, overrule,
5314     oldx = gameInfo.boardWidth,
5315     oldy = gameInfo.boardHeight,
5316     oldh = gameInfo.holdingsWidth,
5317     oldv = gameInfo.variant;
5318
5319     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5320
5321     /* [AS] Initialize pv info list [HGM] and game status */
5322     {
5323         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5324             pvInfoList[i].depth = 0;
5325             boards[i][EP_STATUS] = EP_NONE;
5326             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5327         }
5328
5329         initialRulePlies = 0; /* 50-move counter start */
5330
5331         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5332         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5333     }
5334
5335
5336     /* [HGM] logic here is completely changed. In stead of full positions */
5337     /* the initialized data only consist of the two backranks. The switch */
5338     /* selects which one we will use, which is than copied to the Board   */
5339     /* initialPosition, which for the rest is initialized by Pawns and    */
5340     /* empty squares. This initial position is then copied to boards[0],  */
5341     /* possibly after shuffling, so that it remains available.            */
5342
5343     gameInfo.holdingsWidth = 0; /* default board sizes */
5344     gameInfo.boardWidth    = 8;
5345     gameInfo.boardHeight   = 8;
5346     gameInfo.holdingsSize  = 0;
5347     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5348     for(i=0; i<BOARD_FILES-2; i++)
5349       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5350     initialPosition[EP_STATUS] = EP_NONE;
5351     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5352     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5353          SetCharTable(pieceNickName, appData.pieceNickNames);
5354     else SetCharTable(pieceNickName, "............");
5355
5356     switch (gameInfo.variant) {
5357     case VariantFischeRandom:
5358       shuffleOpenings = TRUE;
5359     default:
5360       pieces = FIDEArray;
5361       break;
5362     case VariantShatranj:
5363       pieces = ShatranjArray;
5364       nrCastlingRights = 0;
5365       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5366       break;
5367     case VariantMakruk:
5368       pieces = makrukArray;
5369       nrCastlingRights = 0;
5370       startedFromSetupPosition = TRUE;
5371       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5372       break;
5373     case VariantTwoKings:
5374       pieces = twoKingsArray;
5375       break;
5376     case VariantCapaRandom:
5377       shuffleOpenings = TRUE;
5378     case VariantCapablanca:
5379       pieces = CapablancaArray;
5380       gameInfo.boardWidth = 10;
5381       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5382       break;
5383     case VariantGothic:
5384       pieces = GothicArray;
5385       gameInfo.boardWidth = 10;
5386       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5387       break;
5388     case VariantJanus:
5389       pieces = JanusArray;
5390       gameInfo.boardWidth = 10;
5391       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5392       nrCastlingRights = 6;
5393         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5394         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5395         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5396         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5397         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5398         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5399       break;
5400     case VariantFalcon:
5401       pieces = FalconArray;
5402       gameInfo.boardWidth = 10;
5403       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5404       break;
5405     case VariantXiangqi:
5406       pieces = XiangqiArray;
5407       gameInfo.boardWidth  = 9;
5408       gameInfo.boardHeight = 10;
5409       nrCastlingRights = 0;
5410       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5411       break;
5412     case VariantShogi:
5413       pieces = ShogiArray;
5414       gameInfo.boardWidth  = 9;
5415       gameInfo.boardHeight = 9;
5416       gameInfo.holdingsSize = 7;
5417       nrCastlingRights = 0;
5418       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5419       break;
5420     case VariantCourier:
5421       pieces = CourierArray;
5422       gameInfo.boardWidth  = 12;
5423       nrCastlingRights = 0;
5424       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5425       break;
5426     case VariantKnightmate:
5427       pieces = KnightmateArray;
5428       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5429       break;
5430     case VariantFairy:
5431       pieces = fairyArray;
5432       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5433       break;
5434     case VariantGreat:
5435       pieces = GreatArray;
5436       gameInfo.boardWidth = 10;
5437       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5438       gameInfo.holdingsSize = 8;
5439       break;
5440     case VariantSuper:
5441       pieces = FIDEArray;
5442       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5443       gameInfo.holdingsSize = 8;
5444       startedFromSetupPosition = TRUE;
5445       break;
5446     case VariantCrazyhouse:
5447     case VariantBughouse:
5448       pieces = FIDEArray;
5449       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5450       gameInfo.holdingsSize = 5;
5451       break;
5452     case VariantWildCastle:
5453       pieces = FIDEArray;
5454       /* !!?shuffle with kings guaranteed to be on d or e file */
5455       shuffleOpenings = 1;
5456       break;
5457     case VariantNoCastle:
5458       pieces = FIDEArray;
5459       nrCastlingRights = 0;
5460       /* !!?unconstrained back-rank shuffle */
5461       shuffleOpenings = 1;
5462       break;
5463     }
5464
5465     overrule = 0;
5466     if(appData.NrFiles >= 0) {
5467         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5468         gameInfo.boardWidth = appData.NrFiles;
5469     }
5470     if(appData.NrRanks >= 0) {
5471         gameInfo.boardHeight = appData.NrRanks;
5472     }
5473     if(appData.holdingsSize >= 0) {
5474         i = appData.holdingsSize;
5475         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5476         gameInfo.holdingsSize = i;
5477     }
5478     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5479     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5480         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5481
5482     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5483     if(pawnRow < 1) pawnRow = 1;
5484     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5485
5486     /* User pieceToChar list overrules defaults */
5487     if(appData.pieceToCharTable != NULL)
5488         SetCharTable(pieceToChar, appData.pieceToCharTable);
5489
5490     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5491
5492         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5493             s = (ChessSquare) 0; /* account holding counts in guard band */
5494         for( i=0; i<BOARD_HEIGHT; i++ )
5495             initialPosition[i][j] = s;
5496
5497         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5498         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5499         initialPosition[pawnRow][j] = WhitePawn;
5500         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5501         if(gameInfo.variant == VariantXiangqi) {
5502             if(j&1) {
5503                 initialPosition[pawnRow][j] =
5504                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5505                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5506                    initialPosition[2][j] = WhiteCannon;
5507                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5508                 }
5509             }
5510         }
5511         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5512     }
5513     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5514
5515             j=BOARD_LEFT+1;
5516             initialPosition[1][j] = WhiteBishop;
5517             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5518             j=BOARD_RGHT-2;
5519             initialPosition[1][j] = WhiteRook;
5520             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5521     }
5522
5523     if( nrCastlingRights == -1) {
5524         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5525         /*       This sets default castling rights from none to normal corners   */
5526         /* Variants with other castling rights must set them themselves above    */
5527         nrCastlingRights = 6;
5528
5529         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5530         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5531         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5532         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5533         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5534         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5535      }
5536
5537      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5538      if(gameInfo.variant == VariantGreat) { // promotion commoners
5539         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5540         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5541         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5542         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5543      }
5544   if (appData.debugMode) {
5545     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5546   }
5547     if(shuffleOpenings) {
5548         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5549         startedFromSetupPosition = TRUE;
5550     }
5551     if(startedFromPositionFile) {
5552       /* [HGM] loadPos: use PositionFile for every new game */
5553       CopyBoard(initialPosition, filePosition);
5554       for(i=0; i<nrCastlingRights; i++)
5555           initialRights[i] = filePosition[CASTLING][i];
5556       startedFromSetupPosition = TRUE;
5557     }
5558
5559     CopyBoard(boards[0], initialPosition);
5560
5561     if(oldx != gameInfo.boardWidth ||
5562        oldy != gameInfo.boardHeight ||
5563        oldh != gameInfo.holdingsWidth
5564 #ifdef GOTHIC
5565        || oldv == VariantGothic ||        // For licensing popups
5566        gameInfo.variant == VariantGothic
5567 #endif
5568 #ifdef FALCON
5569        || oldv == VariantFalcon ||
5570        gameInfo.variant == VariantFalcon
5571 #endif
5572                                          )
5573             InitDrawingSizes(-2 ,0);
5574
5575     if (redraw)
5576       DrawPosition(TRUE, boards[currentMove]);
5577 }
5578
5579 void
5580 SendBoard(cps, moveNum)
5581      ChessProgramState *cps;
5582      int moveNum;
5583 {
5584     char message[MSG_SIZ];
5585
5586     if (cps->useSetboard) {
5587       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5588       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5589       SendToProgram(message, cps);
5590       free(fen);
5591
5592     } else {
5593       ChessSquare *bp;
5594       int i, j;
5595       /* Kludge to set black to move, avoiding the troublesome and now
5596        * deprecated "black" command.
5597        */
5598       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5599
5600       SendToProgram("edit\n", cps);
5601       SendToProgram("#\n", cps);
5602       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5603         bp = &boards[moveNum][i][BOARD_LEFT];
5604         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5605           if ((int) *bp < (int) BlackPawn) {
5606             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5607                     AAA + j, ONE + i);
5608             if(message[0] == '+' || message[0] == '~') {
5609               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5610                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5611                         AAA + j, ONE + i);
5612             }
5613             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5614                 message[1] = BOARD_RGHT   - 1 - j + '1';
5615                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5616             }
5617             SendToProgram(message, cps);
5618           }
5619         }
5620       }
5621
5622       SendToProgram("c\n", cps);
5623       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5624         bp = &boards[moveNum][i][BOARD_LEFT];
5625         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5626           if (((int) *bp != (int) EmptySquare)
5627               && ((int) *bp >= (int) BlackPawn)) {
5628             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5629                     AAA + j, ONE + i);
5630             if(message[0] == '+' || message[0] == '~') {
5631               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5632                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5633                         AAA + j, ONE + i);
5634             }
5635             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5636                 message[1] = BOARD_RGHT   - 1 - j + '1';
5637                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5638             }
5639             SendToProgram(message, cps);
5640           }
5641         }
5642       }
5643
5644       SendToProgram(".\n", cps);
5645     }
5646     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5647 }
5648
5649 static int autoQueen; // [HGM] oneclick
5650
5651 int
5652 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5653 {
5654     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5655     /* [HGM] add Shogi promotions */
5656     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5657     ChessSquare piece;
5658     ChessMove moveType;
5659     Boolean premove;
5660
5661     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5662     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5663
5664     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5665       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5666         return FALSE;
5667
5668     piece = boards[currentMove][fromY][fromX];
5669     if(gameInfo.variant == VariantShogi) {
5670         promotionZoneSize = BOARD_HEIGHT/3;
5671         highestPromotingPiece = (int)WhiteFerz;
5672     } else if(gameInfo.variant == VariantMakruk) {
5673         promotionZoneSize = 3;
5674     }
5675
5676     // next weed out all moves that do not touch the promotion zone at all
5677     if((int)piece >= BlackPawn) {
5678         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5679              return FALSE;
5680         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5681     } else {
5682         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5683            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5684     }
5685
5686     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5687
5688     // weed out mandatory Shogi promotions
5689     if(gameInfo.variant == VariantShogi) {
5690         if(piece >= BlackPawn) {
5691             if(toY == 0 && piece == BlackPawn ||
5692                toY == 0 && piece == BlackQueen ||
5693                toY <= 1 && piece == BlackKnight) {
5694                 *promoChoice = '+';
5695                 return FALSE;
5696             }
5697         } else {
5698             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5699                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5700                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5701                 *promoChoice = '+';
5702                 return FALSE;
5703             }
5704         }
5705     }
5706
5707     // weed out obviously illegal Pawn moves
5708     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5709         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5710         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5711         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5712         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5713         // note we are not allowed to test for valid (non-)capture, due to premove
5714     }
5715
5716     // we either have a choice what to promote to, or (in Shogi) whether to promote
5717     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5718         *promoChoice = PieceToChar(BlackFerz);  // no choice
5719         return FALSE;
5720     }
5721     // no sense asking what we must promote to if it is going to explode...
5722     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5723         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5724         return FALSE;
5725     }
5726     if(autoQueen) { // predetermined
5727         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5728              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5729         else *promoChoice = PieceToChar(BlackQueen);
5730         return FALSE;
5731     }
5732
5733     // suppress promotion popup on illegal moves that are not premoves
5734     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5735               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5736     if(appData.testLegality && !premove) {
5737         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5738                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5739         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5740             return FALSE;
5741     }
5742
5743     return TRUE;
5744 }
5745
5746 int
5747 InPalace(row, column)
5748      int row, column;
5749 {   /* [HGM] for Xiangqi */
5750     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5751          column < (BOARD_WIDTH + 4)/2 &&
5752          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5753     return FALSE;
5754 }
5755
5756 int
5757 PieceForSquare (x, y)
5758      int x;
5759      int y;
5760 {
5761   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5762      return -1;
5763   else
5764      return boards[currentMove][y][x];
5765 }
5766
5767 int
5768 OKToStartUserMove(x, y)
5769      int x, y;
5770 {
5771     ChessSquare from_piece;
5772     int white_piece;
5773
5774     if (matchMode) return FALSE;
5775     if (gameMode == EditPosition) return TRUE;
5776
5777     if (x >= 0 && y >= 0)
5778       from_piece = boards[currentMove][y][x];
5779     else
5780       from_piece = EmptySquare;
5781
5782     if (from_piece == EmptySquare) return FALSE;
5783
5784     white_piece = (int)from_piece >= (int)WhitePawn &&
5785       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5786
5787     switch (gameMode) {
5788       case PlayFromGameFile:
5789       case AnalyzeFile:
5790       case TwoMachinesPlay:
5791       case EndOfGame:
5792         return FALSE;
5793
5794       case IcsObserving:
5795       case IcsIdle:
5796         return FALSE;
5797
5798       case MachinePlaysWhite:
5799       case IcsPlayingBlack:
5800         if (appData.zippyPlay) return FALSE;
5801         if (white_piece) {
5802             DisplayMoveError(_("You are playing Black"));
5803             return FALSE;
5804         }
5805         break;
5806
5807       case MachinePlaysBlack:
5808       case IcsPlayingWhite:
5809         if (appData.zippyPlay) return FALSE;
5810         if (!white_piece) {
5811             DisplayMoveError(_("You are playing White"));
5812             return FALSE;
5813         }
5814         break;
5815
5816       case EditGame:
5817         if (!white_piece && WhiteOnMove(currentMove)) {
5818             DisplayMoveError(_("It is White's turn"));
5819             return FALSE;
5820         }
5821         if (white_piece && !WhiteOnMove(currentMove)) {
5822             DisplayMoveError(_("It is Black's turn"));
5823             return FALSE;
5824         }
5825         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5826             /* Editing correspondence game history */
5827             /* Could disallow this or prompt for confirmation */
5828             cmailOldMove = -1;
5829         }
5830         break;
5831
5832       case BeginningOfGame:
5833         if (appData.icsActive) return FALSE;
5834         if (!appData.noChessProgram) {
5835             if (!white_piece) {
5836                 DisplayMoveError(_("You are playing White"));
5837                 return FALSE;
5838             }
5839         }
5840         break;
5841
5842       case Training:
5843         if (!white_piece && WhiteOnMove(currentMove)) {
5844             DisplayMoveError(_("It is White's turn"));
5845             return FALSE;
5846         }
5847         if (white_piece && !WhiteOnMove(currentMove)) {
5848             DisplayMoveError(_("It is Black's turn"));
5849             return FALSE;
5850         }
5851         break;
5852
5853       default:
5854       case IcsExamining:
5855         break;
5856     }
5857     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5858         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5859         && gameMode != AnalyzeFile && gameMode != Training) {
5860         DisplayMoveError(_("Displayed position is not current"));
5861         return FALSE;
5862     }
5863     return TRUE;
5864 }
5865
5866 Boolean
5867 OnlyMove(int *x, int *y, Boolean captures) {
5868     DisambiguateClosure cl;
5869     if (appData.zippyPlay) return FALSE;
5870     switch(gameMode) {
5871       case MachinePlaysBlack:
5872       case IcsPlayingWhite:
5873       case BeginningOfGame:
5874         if(!WhiteOnMove(currentMove)) return FALSE;
5875         break;
5876       case MachinePlaysWhite:
5877       case IcsPlayingBlack:
5878         if(WhiteOnMove(currentMove)) return FALSE;
5879         break;
5880       case EditGame:
5881         break;
5882       default:
5883         return FALSE;
5884     }
5885     cl.pieceIn = EmptySquare;
5886     cl.rfIn = *y;
5887     cl.ffIn = *x;
5888     cl.rtIn = -1;
5889     cl.ftIn = -1;
5890     cl.promoCharIn = NULLCHAR;
5891     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5892     if( cl.kind == NormalMove ||
5893         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5894         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5895         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5896       fromX = cl.ff;
5897       fromY = cl.rf;
5898       *x = cl.ft;
5899       *y = cl.rt;
5900       return TRUE;
5901     }
5902     if(cl.kind != ImpossibleMove) return FALSE;
5903     cl.pieceIn = EmptySquare;
5904     cl.rfIn = -1;
5905     cl.ffIn = -1;
5906     cl.rtIn = *y;
5907     cl.ftIn = *x;
5908     cl.promoCharIn = NULLCHAR;
5909     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5910     if( cl.kind == NormalMove ||
5911         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5912         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5913         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5914       fromX = cl.ff;
5915       fromY = cl.rf;
5916       *x = cl.ft;
5917       *y = cl.rt;
5918       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5919       return TRUE;
5920     }
5921     return FALSE;
5922 }
5923
5924 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5925 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5926 int lastLoadGameUseList = FALSE;
5927 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5928 ChessMove lastLoadGameStart = EndOfFile;
5929
5930 void
5931 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5932      int fromX, fromY, toX, toY;
5933      int promoChar;
5934 {
5935     ChessMove moveType;
5936     ChessSquare pdown, pup;
5937
5938     /* Check if the user is playing in turn.  This is complicated because we
5939        let the user "pick up" a piece before it is his turn.  So the piece he
5940        tried to pick up may have been captured by the time he puts it down!
5941        Therefore we use the color the user is supposed to be playing in this
5942        test, not the color of the piece that is currently on the starting
5943        square---except in EditGame mode, where the user is playing both
5944        sides; fortunately there the capture race can't happen.  (It can
5945        now happen in IcsExamining mode, but that's just too bad.  The user
5946        will get a somewhat confusing message in that case.)
5947        */
5948
5949     switch (gameMode) {
5950       case PlayFromGameFile:
5951       case AnalyzeFile:
5952       case TwoMachinesPlay:
5953       case EndOfGame:
5954       case IcsObserving:
5955       case IcsIdle:
5956         /* We switched into a game mode where moves are not accepted,
5957            perhaps while the mouse button was down. */
5958         return;
5959
5960       case MachinePlaysWhite:
5961         /* User is moving for Black */
5962         if (WhiteOnMove(currentMove)) {
5963             DisplayMoveError(_("It is White's turn"));
5964             return;
5965         }
5966         break;
5967
5968       case MachinePlaysBlack:
5969         /* User is moving for White */
5970         if (!WhiteOnMove(currentMove)) {
5971             DisplayMoveError(_("It is Black's turn"));
5972             return;
5973         }
5974         break;
5975
5976       case EditGame:
5977       case IcsExamining:
5978       case BeginningOfGame:
5979       case AnalyzeMode:
5980       case Training:
5981         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5982             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5983             /* User is moving for Black */
5984             if (WhiteOnMove(currentMove)) {
5985                 DisplayMoveError(_("It is White's turn"));
5986                 return;
5987             }
5988         } else {
5989             /* User is moving for White */
5990             if (!WhiteOnMove(currentMove)) {
5991                 DisplayMoveError(_("It is Black's turn"));
5992                 return;
5993             }
5994         }
5995         break;
5996
5997       case IcsPlayingBlack:
5998         /* User is moving for Black */
5999         if (WhiteOnMove(currentMove)) {
6000             if (!appData.premove) {
6001                 DisplayMoveError(_("It is White's turn"));
6002             } else if (toX >= 0 && toY >= 0) {
6003                 premoveToX = toX;
6004                 premoveToY = toY;
6005                 premoveFromX = fromX;
6006                 premoveFromY = fromY;
6007                 premovePromoChar = promoChar;
6008                 gotPremove = 1;
6009                 if (appData.debugMode)
6010                     fprintf(debugFP, "Got premove: fromX %d,"
6011                             "fromY %d, toX %d, toY %d\n",
6012                             fromX, fromY, toX, toY);
6013             }
6014             return;
6015         }
6016         break;
6017
6018       case IcsPlayingWhite:
6019         /* User is moving for White */
6020         if (!WhiteOnMove(currentMove)) {
6021             if (!appData.premove) {
6022                 DisplayMoveError(_("It is Black's turn"));
6023             } else if (toX >= 0 && toY >= 0) {
6024                 premoveToX = toX;
6025                 premoveToY = toY;
6026                 premoveFromX = fromX;
6027                 premoveFromY = fromY;
6028                 premovePromoChar = promoChar;
6029                 gotPremove = 1;
6030                 if (appData.debugMode)
6031                     fprintf(debugFP, "Got premove: fromX %d,"
6032                             "fromY %d, toX %d, toY %d\n",
6033                             fromX, fromY, toX, toY);
6034             }
6035             return;
6036         }
6037         break;
6038
6039       default:
6040         break;
6041
6042       case EditPosition:
6043         /* EditPosition, empty square, or different color piece;
6044            click-click move is possible */
6045         if (toX == -2 || toY == -2) {
6046             boards[0][fromY][fromX] = EmptySquare;
6047             DrawPosition(FALSE, boards[currentMove]);
6048             return;
6049         } else if (toX >= 0 && toY >= 0) {
6050             boards[0][toY][toX] = boards[0][fromY][fromX];
6051             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6052                 if(boards[0][fromY][0] != EmptySquare) {
6053                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6054                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6055                 }
6056             } else
6057             if(fromX == BOARD_RGHT+1) {
6058                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6059                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6060                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6061                 }
6062             } else
6063             boards[0][fromY][fromX] = EmptySquare;
6064             DrawPosition(FALSE, boards[currentMove]);
6065             return;
6066         }
6067         return;
6068     }
6069
6070     if(toX < 0 || toY < 0) return;
6071     pdown = boards[currentMove][fromY][fromX];
6072     pup = boards[currentMove][toY][toX];
6073
6074     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6075     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
6076          if( pup != EmptySquare ) return;
6077          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6078            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6079                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6080            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6081            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6082            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6083            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6084          fromY = DROP_RANK;
6085     }
6086
6087     /* [HGM] always test for legality, to get promotion info */
6088     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6089                                          fromY, fromX, toY, toX, promoChar);
6090     /* [HGM] but possibly ignore an IllegalMove result */
6091     if (appData.testLegality) {
6092         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6093             DisplayMoveError(_("Illegal move"));
6094             return;
6095         }
6096     }
6097
6098     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6099 }
6100
6101 /* Common tail of UserMoveEvent and DropMenuEvent */
6102 int
6103 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6104      ChessMove moveType;
6105      int fromX, fromY, toX, toY;
6106      /*char*/int promoChar;
6107 {
6108     char *bookHit = 0;
6109
6110     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6111         // [HGM] superchess: suppress promotions to non-available piece
6112         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6113         if(WhiteOnMove(currentMove)) {
6114             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6115         } else {
6116             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6117         }
6118     }
6119
6120     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6121        move type in caller when we know the move is a legal promotion */
6122     if(moveType == NormalMove && promoChar)
6123         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6124
6125     /* [HGM] <popupFix> The following if has been moved here from
6126        UserMoveEvent(). Because it seemed to belong here (why not allow
6127        piece drops in training games?), and because it can only be
6128        performed after it is known to what we promote. */
6129     if (gameMode == Training) {
6130       /* compare the move played on the board to the next move in the
6131        * game. If they match, display the move and the opponent's response.
6132        * If they don't match, display an error message.
6133        */
6134       int saveAnimate;
6135       Board testBoard;
6136       CopyBoard(testBoard, boards[currentMove]);
6137       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6138
6139       if (CompareBoards(testBoard, boards[currentMove+1])) {
6140         ForwardInner(currentMove+1);
6141
6142         /* Autoplay the opponent's response.
6143          * if appData.animate was TRUE when Training mode was entered,
6144          * the response will be animated.
6145          */
6146         saveAnimate = appData.animate;
6147         appData.animate = animateTraining;
6148         ForwardInner(currentMove+1);
6149         appData.animate = saveAnimate;
6150
6151         /* check for the end of the game */
6152         if (currentMove >= forwardMostMove) {
6153           gameMode = PlayFromGameFile;
6154           ModeHighlight();
6155           SetTrainingModeOff();
6156           DisplayInformation(_("End of game"));
6157         }
6158       } else {
6159         DisplayError(_("Incorrect move"), 0);
6160       }
6161       return 1;
6162     }
6163
6164   /* Ok, now we know that the move is good, so we can kill
6165      the previous line in Analysis Mode */
6166   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6167                                 && currentMove < forwardMostMove) {
6168     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6169     else forwardMostMove = currentMove;
6170   }
6171
6172   /* If we need the chess program but it's dead, restart it */
6173   ResurrectChessProgram();
6174
6175   /* A user move restarts a paused game*/
6176   if (pausing)
6177     PauseEvent();
6178
6179   thinkOutput[0] = NULLCHAR;
6180
6181   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6182
6183   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6184
6185   if (gameMode == BeginningOfGame) {
6186     if (appData.noChessProgram) {
6187       gameMode = EditGame;
6188       SetGameInfo();
6189     } else {
6190       char buf[MSG_SIZ];
6191       gameMode = MachinePlaysBlack;
6192       StartClocks();
6193       SetGameInfo();
6194       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6195       DisplayTitle(buf);
6196       if (first.sendName) {
6197         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6198         SendToProgram(buf, &first);
6199       }
6200       StartClocks();
6201     }
6202     ModeHighlight();
6203   }
6204
6205   /* Relay move to ICS or chess engine */
6206   if (appData.icsActive) {
6207     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6208         gameMode == IcsExamining) {
6209       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6210         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6211         SendToICS("draw ");
6212         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6213       }
6214       // also send plain move, in case ICS does not understand atomic claims
6215       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6216       ics_user_moved = 1;
6217     }
6218   } else {
6219     if (first.sendTime && (gameMode == BeginningOfGame ||
6220                            gameMode == MachinePlaysWhite ||
6221                            gameMode == MachinePlaysBlack)) {
6222       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6223     }
6224     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6225          // [HGM] book: if program might be playing, let it use book
6226         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6227         first.maybeThinking = TRUE;
6228     } else SendMoveToProgram(forwardMostMove-1, &first);
6229     if (currentMove == cmailOldMove + 1) {
6230       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6231     }
6232   }
6233
6234   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6235
6236   switch (gameMode) {
6237   case EditGame:
6238     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6239     case MT_NONE:
6240     case MT_CHECK:
6241       break;
6242     case MT_CHECKMATE:
6243     case MT_STAINMATE:
6244       if (WhiteOnMove(currentMove)) {
6245         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6246       } else {
6247         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6248       }
6249       break;
6250     case MT_STALEMATE:
6251       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6252       break;
6253     }
6254     break;
6255
6256   case MachinePlaysBlack:
6257   case MachinePlaysWhite:
6258     /* disable certain menu options while machine is thinking */
6259     SetMachineThinkingEnables();
6260     break;
6261
6262   default:
6263     break;
6264   }
6265
6266   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6267
6268   if(bookHit) { // [HGM] book: simulate book reply
6269         static char bookMove[MSG_SIZ]; // a bit generous?
6270
6271         programStats.nodes = programStats.depth = programStats.time =
6272         programStats.score = programStats.got_only_move = 0;
6273         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6274
6275         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6276         strcat(bookMove, bookHit);
6277         HandleMachineMove(bookMove, &first);
6278   }
6279   return 1;
6280 }
6281
6282 void
6283 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6284      Board board;
6285      int flags;
6286      ChessMove kind;
6287      int rf, ff, rt, ft;
6288      VOIDSTAR closure;
6289 {
6290     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6291     Markers *m = (Markers *) closure;
6292     if(rf == fromY && ff == fromX)
6293         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6294                          || kind == WhiteCapturesEnPassant
6295                          || kind == BlackCapturesEnPassant);
6296     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6297 }
6298
6299 void
6300 MarkTargetSquares(int clear)
6301 {
6302   int x, y;
6303   if(!appData.markers || !appData.highlightDragging ||
6304      !appData.testLegality || gameMode == EditPosition) return;
6305   if(clear) {
6306     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6307   } else {
6308     int capt = 0;
6309     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6310     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6311       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6312       if(capt)
6313       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6314     }
6315   }
6316   DrawPosition(TRUE, NULL);
6317 }
6318
6319 int
6320 Explode(Board board, int fromX, int fromY, int toX, int toY)
6321 {
6322     if(gameInfo.variant == VariantAtomic &&
6323        (board[toY][toX] != EmptySquare ||                     // capture?
6324         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6325                          board[fromY][fromX] == BlackPawn   )
6326       )) {
6327         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6328         return TRUE;
6329     }
6330     return FALSE;
6331 }
6332
6333 void LeftClick(ClickType clickType, int xPix, int yPix)
6334 {
6335     int x, y;
6336     Boolean saveAnimate;
6337     static int second = 0, promotionChoice = 0, dragging = 0;
6338     char promoChoice = NULLCHAR;
6339
6340     if(appData.seekGraph && appData.icsActive && loggedOn &&
6341         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6342         SeekGraphClick(clickType, xPix, yPix, 0);
6343         return;
6344     }
6345
6346     if (clickType == Press) ErrorPopDown();
6347     MarkTargetSquares(1);
6348
6349     x = EventToSquare(xPix, BOARD_WIDTH);
6350     y = EventToSquare(yPix, BOARD_HEIGHT);
6351     if (!flipView && y >= 0) {
6352         y = BOARD_HEIGHT - 1 - y;
6353     }
6354     if (flipView && x >= 0) {
6355         x = BOARD_WIDTH - 1 - x;
6356     }
6357
6358     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6359         if(clickType == Release) return; // ignore upclick of click-click destination
6360         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6361         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6362         if(gameInfo.holdingsWidth &&
6363                 (WhiteOnMove(currentMove)
6364                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6365                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6366             // click in right holdings, for determining promotion piece
6367             ChessSquare p = boards[currentMove][y][x];
6368             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6369             if(p != EmptySquare) {
6370                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6371                 fromX = fromY = -1;
6372                 return;
6373             }
6374         }
6375         DrawPosition(FALSE, boards[currentMove]);
6376         return;
6377     }
6378
6379     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6380     if(clickType == Press
6381             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6382               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6383               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6384         return;
6385
6386     autoQueen = appData.alwaysPromoteToQueen;
6387
6388     if (fromX == -1) {
6389       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6390         if (clickType == Press) {
6391             /* First square */
6392             if (OKToStartUserMove(x, y)) {
6393                 fromX = x;
6394                 fromY = y;
6395                 second = 0;
6396                 MarkTargetSquares(0);
6397                 DragPieceBegin(xPix, yPix); dragging = 1;
6398                 if (appData.highlightDragging) {
6399                     SetHighlights(x, y, -1, -1);
6400                 }
6401             }
6402         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6403             DragPieceEnd(xPix, yPix); dragging = 0;
6404             DrawPosition(FALSE, NULL);
6405         }
6406         return;
6407       }
6408     }
6409
6410     /* fromX != -1 */
6411     if (clickType == Press && gameMode != EditPosition) {
6412         ChessSquare fromP;
6413         ChessSquare toP;
6414         int frc;
6415
6416         // ignore off-board to clicks
6417         if(y < 0 || x < 0) return;
6418
6419         /* Check if clicking again on the same color piece */
6420         fromP = boards[currentMove][fromY][fromX];
6421         toP = boards[currentMove][y][x];
6422         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6423         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6424              WhitePawn <= toP && toP <= WhiteKing &&
6425              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6426              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6427             (BlackPawn <= fromP && fromP <= BlackKing &&
6428              BlackPawn <= toP && toP <= BlackKing &&
6429              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6430              !(fromP == BlackKing && toP == BlackRook && frc))) {
6431             /* Clicked again on same color piece -- changed his mind */
6432             second = (x == fromX && y == fromY);
6433            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6434             if (appData.highlightDragging) {
6435                 SetHighlights(x, y, -1, -1);
6436             } else {
6437                 ClearHighlights();
6438             }
6439             if (OKToStartUserMove(x, y)) {
6440                 fromX = x;
6441                 fromY = y; dragging = 1;
6442                 MarkTargetSquares(0);
6443                 DragPieceBegin(xPix, yPix);
6444             }
6445             return;
6446            }
6447         }
6448         // ignore clicks on holdings
6449         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6450     }
6451
6452     if (clickType == Release && x == fromX && y == fromY) {
6453         DragPieceEnd(xPix, yPix); dragging = 0;
6454         if (appData.animateDragging) {
6455             /* Undo animation damage if any */
6456             DrawPosition(FALSE, NULL);
6457         }
6458         if (second) {
6459             /* Second up/down in same square; just abort move */
6460             second = 0;
6461             fromX = fromY = -1;
6462             ClearHighlights();
6463             gotPremove = 0;
6464             ClearPremoveHighlights();
6465         } else {
6466             /* First upclick in same square; start click-click mode */
6467             SetHighlights(x, y, -1, -1);
6468         }
6469         return;
6470     }
6471
6472     /* we now have a different from- and (possibly off-board) to-square */
6473     /* Completed move */
6474     toX = x;
6475     toY = y;
6476     saveAnimate = appData.animate;
6477     if (clickType == Press) {
6478         /* Finish clickclick move */
6479         if (appData.animate || appData.highlightLastMove) {
6480             SetHighlights(fromX, fromY, toX, toY);
6481         } else {
6482             ClearHighlights();
6483         }
6484     } else {
6485         /* Finish drag move */
6486         if (appData.highlightLastMove) {
6487             SetHighlights(fromX, fromY, toX, toY);
6488         } else {
6489             ClearHighlights();
6490         }
6491         DragPieceEnd(xPix, yPix); dragging = 0;
6492         /* Don't animate move and drag both */
6493         appData.animate = FALSE;
6494     }
6495
6496     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6497     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6498         ChessSquare piece = boards[currentMove][fromY][fromX];
6499         if(gameMode == EditPosition && piece != EmptySquare &&
6500            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6501             int n;
6502
6503             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6504                 n = PieceToNumber(piece - (int)BlackPawn);
6505                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6506                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6507                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6508             } else
6509             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6510                 n = PieceToNumber(piece);
6511                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6512                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6513                 boards[currentMove][n][BOARD_WIDTH-2]++;
6514             }
6515             boards[currentMove][fromY][fromX] = EmptySquare;
6516         }
6517         ClearHighlights();
6518         fromX = fromY = -1;
6519         DrawPosition(TRUE, boards[currentMove]);
6520         return;
6521     }
6522
6523     // off-board moves should not be highlighted
6524     if(x < 0 || x < 0) ClearHighlights();
6525
6526     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6527         SetHighlights(fromX, fromY, toX, toY);
6528         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6529             // [HGM] super: promotion to captured piece selected from holdings
6530             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6531             promotionChoice = TRUE;
6532             // kludge follows to temporarily execute move on display, without promoting yet
6533             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6534             boards[currentMove][toY][toX] = p;
6535             DrawPosition(FALSE, boards[currentMove]);
6536             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6537             boards[currentMove][toY][toX] = q;
6538             DisplayMessage("Click in holdings to choose piece", "");
6539             return;
6540         }
6541         PromotionPopUp();
6542     } else {
6543         int oldMove = currentMove;
6544         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6545         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6546         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6547         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6548            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6549             DrawPosition(TRUE, boards[currentMove]);
6550         fromX = fromY = -1;
6551     }
6552     appData.animate = saveAnimate;
6553     if (appData.animate || appData.animateDragging) {
6554         /* Undo animation damage if needed */
6555         DrawPosition(FALSE, NULL);
6556     }
6557 }
6558
6559 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6560 {   // front-end-free part taken out of PieceMenuPopup
6561     int whichMenu; int xSqr, ySqr;
6562
6563     if(seekGraphUp) { // [HGM] seekgraph
6564         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6565         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6566         return -2;
6567     }
6568
6569     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6570          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6571         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6572         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6573         if(action == Press)   {
6574             originalFlip = flipView;
6575             flipView = !flipView; // temporarily flip board to see game from partners perspective
6576             DrawPosition(TRUE, partnerBoard);
6577             DisplayMessage(partnerStatus, "");
6578             partnerUp = TRUE;
6579         } else if(action == Release) {
6580             flipView = originalFlip;
6581             DrawPosition(TRUE, boards[currentMove]);
6582             partnerUp = FALSE;
6583         }
6584         return -2;
6585     }
6586
6587     xSqr = EventToSquare(x, BOARD_WIDTH);
6588     ySqr = EventToSquare(y, BOARD_HEIGHT);
6589     if (action == Release) UnLoadPV(); // [HGM] pv
6590     if (action != Press) return -2; // return code to be ignored
6591     switch (gameMode) {
6592       case IcsExamining:
6593         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6594       case EditPosition:
6595         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6596         if (xSqr < 0 || ySqr < 0) return -1;\r
6597         whichMenu = 0; // edit-position menu
6598         break;
6599       case IcsObserving:
6600         if(!appData.icsEngineAnalyze) return -1;
6601       case IcsPlayingWhite:
6602       case IcsPlayingBlack:
6603         if(!appData.zippyPlay) goto noZip;
6604       case AnalyzeMode:
6605       case AnalyzeFile:
6606       case MachinePlaysWhite:
6607       case MachinePlaysBlack:
6608       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6609         if (!appData.dropMenu) {
6610           LoadPV(x, y);
6611           return 2; // flag front-end to grab mouse events
6612         }
6613         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6614            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6615       case EditGame:
6616       noZip:
6617         if (xSqr < 0 || ySqr < 0) return -1;
6618         if (!appData.dropMenu || appData.testLegality &&
6619             gameInfo.variant != VariantBughouse &&
6620             gameInfo.variant != VariantCrazyhouse) return -1;
6621         whichMenu = 1; // drop menu
6622         break;
6623       default:
6624         return -1;
6625     }
6626
6627     if (((*fromX = xSqr) < 0) ||
6628         ((*fromY = ySqr) < 0)) {
6629         *fromX = *fromY = -1;
6630         return -1;
6631     }
6632     if (flipView)
6633       *fromX = BOARD_WIDTH - 1 - *fromX;
6634     else
6635       *fromY = BOARD_HEIGHT - 1 - *fromY;
6636
6637     return whichMenu;
6638 }
6639
6640 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6641 {
6642 //    char * hint = lastHint;
6643     FrontEndProgramStats stats;
6644
6645     stats.which = cps == &first ? 0 : 1;
6646     stats.depth = cpstats->depth;
6647     stats.nodes = cpstats->nodes;
6648     stats.score = cpstats->score;
6649     stats.time = cpstats->time;
6650     stats.pv = cpstats->movelist;
6651     stats.hint = lastHint;
6652     stats.an_move_index = 0;
6653     stats.an_move_count = 0;
6654
6655     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6656         stats.hint = cpstats->move_name;
6657         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6658         stats.an_move_count = cpstats->nr_moves;
6659     }
6660
6661     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
6662
6663     SetProgramStats( &stats );
6664 }
6665
6666 void
6667 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6668 {       // count all piece types
6669         int p, f, r;
6670         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6671         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6672         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6673                 p = board[r][f];
6674                 pCnt[p]++;
6675                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6676                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6677                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6678                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6679                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6680                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6681         }
6682 }
6683
6684 int
6685 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6686 {
6687         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6688         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6689
6690         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6691         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6692         if(myPawns == 2 && nMine == 3) // KPP
6693             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6694         if(myPawns == 1 && nMine == 2) // KP
6695             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6696         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6697             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6698         if(myPawns) return FALSE;
6699         if(pCnt[WhiteRook+side])
6700             return pCnt[BlackRook-side] ||
6701                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6702                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6703                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6704         if(pCnt[WhiteCannon+side]) {
6705             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6706             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6707         }
6708         if(pCnt[WhiteKnight+side])
6709             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6710         return FALSE;
6711 }
6712
6713 int
6714 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6715 {
6716         VariantClass v = gameInfo.variant;
6717
6718         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6719         if(v == VariantShatranj) return TRUE; // always winnable through baring
6720         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6721         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6722
6723         if(v == VariantXiangqi) {
6724                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6725
6726                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6727                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6728                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6729                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6730                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6731                 if(stale) // we have at least one last-rank P plus perhaps C
6732                     return majors // KPKX
6733                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6734                 else // KCA*E*
6735                     return pCnt[WhiteFerz+side] // KCAK
6736                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6737                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6738                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6739
6740         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6741                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6742
6743                 if(nMine == 1) return FALSE; // bare King
6744                 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
6745                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6746                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6747                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6748                 if(pCnt[WhiteKnight+side])
6749                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6750                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6751                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6752                 if(nBishops)
6753                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6754                 if(pCnt[WhiteAlfil+side])
6755                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6756                 if(pCnt[WhiteWazir+side])
6757                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6758         }
6759
6760         return TRUE;
6761 }
6762
6763 int
6764 Adjudicate(ChessProgramState *cps)
6765 {       // [HGM] some adjudications useful with buggy engines
6766         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6767         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6768         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6769         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6770         int k, count = 0; static int bare = 1;
6771         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6772         Boolean canAdjudicate = !appData.icsActive;
6773
6774         // most tests only when we understand the game, i.e. legality-checking on
6775             if( appData.testLegality )
6776             {   /* [HGM] Some more adjudications for obstinate engines */
6777                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6778                 static int moveCount = 6;
6779                 ChessMove result;
6780                 char *reason = NULL;
6781
6782                 /* Count what is on board. */
6783                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6784
6785                 /* Some material-based adjudications that have to be made before stalemate test */
6786                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6787                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6788                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6789                      if(canAdjudicate && appData.checkMates) {
6790                          if(engineOpponent)
6791                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6792                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6793                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6794                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6795                          return 1;
6796                      }
6797                 }
6798
6799                 /* Bare King in Shatranj (loses) or Losers (wins) */
6800                 if( nrW == 1 || nrB == 1) {
6801                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6802                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6803                      if(canAdjudicate && appData.checkMates) {
6804                          if(engineOpponent)
6805                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6806                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6807                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6808                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6809                          return 1;
6810                      }
6811                   } else
6812                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6813                   {    /* bare King */
6814                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6815                         if(canAdjudicate && appData.checkMates) {
6816                             /* but only adjudicate if adjudication enabled */
6817                             if(engineOpponent)
6818                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6819                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6820                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6821                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6822                             return 1;
6823                         }
6824                   }
6825                 } else bare = 1;
6826
6827
6828             // don't wait for engine to announce game end if we can judge ourselves
6829             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6830               case MT_CHECK:
6831                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6832                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6833                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6834                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6835                             checkCnt++;
6836                         if(checkCnt >= 2) {
6837                             reason = "Xboard adjudication: 3rd check";
6838                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6839                             break;
6840                         }
6841                     }
6842                 }
6843               case MT_NONE:
6844               default:
6845                 break;
6846               case MT_STALEMATE:
6847               case MT_STAINMATE:
6848                 reason = "Xboard adjudication: Stalemate";
6849                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6850                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6851                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6852                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6853                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6854                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6855                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6856                                                                         EP_CHECKMATE : EP_WINS);
6857                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6858                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6859                 }
6860                 break;
6861               case MT_CHECKMATE:
6862                 reason = "Xboard adjudication: Checkmate";
6863                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6864                 break;
6865             }
6866
6867                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6868                     case EP_STALEMATE:
6869                         result = GameIsDrawn; break;
6870                     case EP_CHECKMATE:
6871                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6872                     case EP_WINS:
6873                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6874                     default:
6875                         result = EndOfFile;
6876                 }
6877                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6878                     if(engineOpponent)
6879                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6880                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6881                     GameEnds( result, reason, GE_XBOARD );
6882                     return 1;
6883                 }
6884
6885                 /* Next absolutely insufficient mating material. */
6886                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6887                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6888                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6889
6890                      /* always flag draws, for judging claims */
6891                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6892
6893                      if(canAdjudicate && appData.materialDraws) {
6894                          /* but only adjudicate them if adjudication enabled */
6895                          if(engineOpponent) {
6896                            SendToProgram("force\n", engineOpponent); // suppress reply
6897                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6898                          }
6899                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6900                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6901                          return 1;
6902                      }
6903                 }
6904
6905                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6906                 if(gameInfo.variant == VariantXiangqi ?
6907                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6908                  : nrW + nrB == 4 &&
6909                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6910                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6911                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6912                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6913                    ) ) {
6914                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6915                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6916                           if(engineOpponent) {
6917                             SendToProgram("force\n", engineOpponent); // suppress reply
6918                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6919                           }
6920                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6921                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6922                           return 1;
6923                      }
6924                 } else moveCount = 6;
6925             }
6926         if (appData.debugMode) { int i;
6927             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6928                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6929                     appData.drawRepeats);
6930             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6931               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6932
6933         }
6934
6935         // Repetition draws and 50-move rule can be applied independently of legality testing
6936
6937                 /* Check for rep-draws */
6938                 count = 0;
6939                 for(k = forwardMostMove-2;
6940                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6941                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6942                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6943                     k-=2)
6944                 {   int rights=0;
6945                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6946                         /* compare castling rights */
6947                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6948                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6949                                 rights++; /* King lost rights, while rook still had them */
6950                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6951                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6952                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6953                                    rights++; /* but at least one rook lost them */
6954                         }
6955                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6956                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6957                                 rights++;
6958                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6959                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6960                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6961                                    rights++;
6962                         }
6963                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6964                             && appData.drawRepeats > 1) {
6965                              /* adjudicate after user-specified nr of repeats */
6966                              int result = GameIsDrawn;
6967                              char *details = "XBoard adjudication: repetition draw";
6968                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6969                                 // [HGM] xiangqi: check for forbidden perpetuals
6970                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6971                                 for(m=forwardMostMove; m>k; m-=2) {
6972                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6973                                         ourPerpetual = 0; // the current mover did not always check
6974                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6975                                         hisPerpetual = 0; // the opponent did not always check
6976                                 }
6977                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6978                                                                         ourPerpetual, hisPerpetual);
6979                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6980                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6981                                     details = "Xboard adjudication: perpetual checking";
6982                                 } else
6983                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6984                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6985                                 } else
6986                                 // Now check for perpetual chases
6987                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6988                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6989                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6990                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6991                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6992                                         details = "Xboard adjudication: perpetual chasing";
6993                                     } else
6994                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6995                                         break; // Abort repetition-checking loop.
6996                                 }
6997                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6998                              }
6999                              if(engineOpponent) {
7000                                SendToProgram("force\n", engineOpponent); // suppress reply
7001                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7002                              }
7003                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7004                              GameEnds( result, details, GE_XBOARD );
7005                              return 1;
7006                         }
7007                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7008                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7009                     }
7010                 }
7011
7012                 /* Now we test for 50-move draws. Determine ply count */
7013                 count = forwardMostMove;
7014                 /* look for last irreversble move */
7015                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7016                     count--;
7017                 /* if we hit starting position, add initial plies */
7018                 if( count == backwardMostMove )
7019                     count -= initialRulePlies;
7020                 count = forwardMostMove - count;
7021                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7022                         // adjust reversible move counter for checks in Xiangqi
7023                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7024                         if(i < backwardMostMove) i = backwardMostMove;
7025                         while(i <= forwardMostMove) {
7026                                 lastCheck = inCheck; // check evasion does not count
7027                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7028                                 if(inCheck || lastCheck) count--; // check does not count
7029                                 i++;
7030                         }
7031                 }
7032                 if( count >= 100)
7033                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7034                          /* this is used to judge if draw claims are legal */
7035                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
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, "Xboard adjudication: 50-move rule", GE_XBOARD );
7042                          return 1;
7043                 }
7044
7045                 /* if draw offer is pending, treat it as a draw claim
7046                  * when draw condition present, to allow engines a way to
7047                  * claim draws before making their move to avoid a race
7048                  * condition occurring after their move
7049                  */
7050                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7051                          char *p = NULL;
7052                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7053                              p = "Draw claim: 50-move rule";
7054                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7055                              p = "Draw claim: 3-fold repetition";
7056                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7057                              p = "Draw claim: insufficient mating material";
7058                          if( p != NULL && canAdjudicate) {
7059                              if(engineOpponent) {
7060                                SendToProgram("force\n", engineOpponent); // suppress reply
7061                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7062                              }
7063                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7064                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7065                              return 1;
7066                          }
7067                 }
7068
7069                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7070                     if(engineOpponent) {
7071                       SendToProgram("force\n", engineOpponent); // suppress reply
7072                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7073                     }
7074                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7075                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7076                     return 1;
7077                 }
7078         return 0;
7079 }
7080
7081 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7082 {   // [HGM] book: this routine intercepts moves to simulate book replies
7083     char *bookHit = NULL;
7084
7085     //first determine if the incoming move brings opponent into his book
7086     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7087         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7088     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7089     if(bookHit != NULL && !cps->bookSuspend) {
7090         // make sure opponent is not going to reply after receiving move to book position
7091         SendToProgram("force\n", cps);
7092         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7093     }
7094     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7095     // now arrange restart after book miss
7096     if(bookHit) {
7097         // after a book hit we never send 'go', and the code after the call to this routine
7098         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7099         char buf[MSG_SIZ];
7100         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7101         SendToProgram(buf, cps);
7102         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7103     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7104         SendToProgram("go\n", cps);
7105         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7106     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7107         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7108             SendToProgram("go\n", cps);
7109         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7110     }
7111     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7112 }
7113
7114 char *savedMessage;
7115 ChessProgramState *savedState;
7116 void DeferredBookMove(void)
7117 {
7118         if(savedState->lastPing != savedState->lastPong)
7119                     ScheduleDelayedEvent(DeferredBookMove, 10);
7120         else
7121         HandleMachineMove(savedMessage, savedState);
7122 }
7123
7124 void
7125 HandleMachineMove(message, cps)
7126      char *message;
7127      ChessProgramState *cps;
7128 {
7129     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7130     char realname[MSG_SIZ];
7131     int fromX, fromY, toX, toY;
7132     ChessMove moveType;
7133     char promoChar;
7134     char *p;
7135     int machineWhite;
7136     char *bookHit;
7137
7138     cps->userError = 0;
7139
7140 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7141     /*
7142      * Kludge to ignore BEL characters
7143      */
7144     while (*message == '\007') message++;
7145
7146     /*
7147      * [HGM] engine debug message: ignore lines starting with '#' character
7148      */
7149     if(cps->debug && *message == '#') return;
7150
7151     /*
7152      * Look for book output
7153      */
7154     if (cps == &first && bookRequested) {
7155         if (message[0] == '\t' || message[0] == ' ') {
7156             /* Part of the book output is here; append it */
7157             strcat(bookOutput, message);
7158             strcat(bookOutput, "  \n");
7159             return;
7160         } else if (bookOutput[0] != NULLCHAR) {
7161             /* All of book output has arrived; display it */
7162             char *p = bookOutput;
7163             while (*p != NULLCHAR) {
7164                 if (*p == '\t') *p = ' ';
7165                 p++;
7166             }
7167             DisplayInformation(bookOutput);
7168             bookRequested = FALSE;
7169             /* Fall through to parse the current output */
7170         }
7171     }
7172
7173     /*
7174      * Look for machine move.
7175      */
7176     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7177         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7178     {
7179         /* This method is only useful on engines that support ping */
7180         if (cps->lastPing != cps->lastPong) {
7181           if (gameMode == BeginningOfGame) {
7182             /* Extra move from before last new; ignore */
7183             if (appData.debugMode) {
7184                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7185             }
7186           } else {
7187             if (appData.debugMode) {
7188                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7189                         cps->which, gameMode);
7190             }
7191
7192             SendToProgram("undo\n", cps);
7193           }
7194           return;
7195         }
7196
7197         switch (gameMode) {
7198           case BeginningOfGame:
7199             /* Extra move from before last reset; ignore */
7200             if (appData.debugMode) {
7201                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7202             }
7203             return;
7204
7205           case EndOfGame:
7206           case IcsIdle:
7207           default:
7208             /* Extra move after we tried to stop.  The mode test is
7209                not a reliable way of detecting this problem, but it's
7210                the best we can do on engines that don't support ping.
7211             */
7212             if (appData.debugMode) {
7213                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7214                         cps->which, gameMode);
7215             }
7216             SendToProgram("undo\n", cps);
7217             return;
7218
7219           case MachinePlaysWhite:
7220           case IcsPlayingWhite:
7221             machineWhite = TRUE;
7222             break;
7223
7224           case MachinePlaysBlack:
7225           case IcsPlayingBlack:
7226             machineWhite = FALSE;
7227             break;
7228
7229           case TwoMachinesPlay:
7230             machineWhite = (cps->twoMachinesColor[0] == 'w');
7231             break;
7232         }
7233         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7234             if (appData.debugMode) {
7235                 fprintf(debugFP,
7236                         "Ignoring move out of turn by %s, gameMode %d"
7237                         ", forwardMost %d\n",
7238                         cps->which, gameMode, forwardMostMove);
7239             }
7240             return;
7241         }
7242
7243     if (appData.debugMode) { int f = forwardMostMove;
7244         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7245                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7246                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7247     }
7248         if(cps->alphaRank) AlphaRank(machineMove, 4);
7249         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7250                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7251             /* Machine move could not be parsed; ignore it. */
7252           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7253                     machineMove, cps->which);
7254             DisplayError(buf1, 0);
7255             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7256                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7257             if (gameMode == TwoMachinesPlay) {
7258               GameEnds(machineWhite ? BlackWins : WhiteWins,
7259                        buf1, GE_XBOARD);
7260             }
7261             return;
7262         }
7263
7264         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7265         /* So we have to redo legality test with true e.p. status here,  */
7266         /* to make sure an illegal e.p. capture does not slip through,   */
7267         /* to cause a forfeit on a justified illegal-move complaint      */
7268         /* of the opponent.                                              */
7269         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7270            ChessMove moveType;
7271            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7272                              fromY, fromX, toY, toX, promoChar);
7273             if (appData.debugMode) {
7274                 int i;
7275                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7276                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7277                 fprintf(debugFP, "castling rights\n");
7278             }
7279             if(moveType == IllegalMove) {
7280               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7281                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7282                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7283                            buf1, GE_XBOARD);
7284                 return;
7285            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7286            /* [HGM] Kludge to handle engines that send FRC-style castling
7287               when they shouldn't (like TSCP-Gothic) */
7288            switch(moveType) {
7289              case WhiteASideCastleFR:
7290              case BlackASideCastleFR:
7291                toX+=2;
7292                currentMoveString[2]++;
7293                break;
7294              case WhiteHSideCastleFR:
7295              case BlackHSideCastleFR:
7296                toX--;
7297                currentMoveString[2]--;
7298                break;
7299              default: ; // nothing to do, but suppresses warning of pedantic compilers
7300            }
7301         }
7302         hintRequested = FALSE;
7303         lastHint[0] = NULLCHAR;
7304         bookRequested = FALSE;
7305         /* Program may be pondering now */
7306         cps->maybeThinking = TRUE;
7307         if (cps->sendTime == 2) cps->sendTime = 1;
7308         if (cps->offeredDraw) cps->offeredDraw--;
7309
7310         /* currentMoveString is set as a side-effect of ParseOneMove */
7311         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7312         strcat(machineMove, "\n");
7313         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7314
7315         /* [AS] Save move info*/
7316         pvInfoList[ forwardMostMove ].score = programStats.score;
7317         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7318         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7319
7320         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7321
7322         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7323         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7324             int count = 0;
7325
7326             while( count < adjudicateLossPlies ) {
7327                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7328
7329                 if( count & 1 ) {
7330                     score = -score; /* Flip score for winning side */
7331                 }
7332
7333                 if( score > adjudicateLossThreshold ) {
7334                     break;
7335                 }
7336
7337                 count++;
7338             }
7339
7340             if( count >= adjudicateLossPlies ) {
7341                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7342
7343                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7344                     "Xboard adjudication",
7345                     GE_XBOARD );
7346
7347                 return;
7348             }
7349         }
7350
7351         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7352
7353 #if ZIPPY
7354         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7355             first.initDone) {
7356           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7357                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7358                 SendToICS("draw ");
7359                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7360           }
7361           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7362           ics_user_moved = 1;
7363           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7364                 char buf[3*MSG_SIZ];
7365
7366                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7367                         programStats.score / 100.,
7368                         programStats.depth,
7369                         programStats.time / 100.,
7370                         (unsigned int)programStats.nodes,
7371                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7372                         programStats.movelist);
7373                 SendToICS(buf);
7374 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7375           }
7376         }
7377 #endif
7378
7379         /* [AS] Clear stats for next move */
7380         ClearProgramStats();
7381         thinkOutput[0] = NULLCHAR;
7382         hiddenThinkOutputState = 0;
7383
7384         bookHit = NULL;
7385         if (gameMode == TwoMachinesPlay) {
7386             /* [HGM] relaying draw offers moved to after reception of move */
7387             /* and interpreting offer as claim if it brings draw condition */
7388             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7389                 SendToProgram("draw\n", cps->other);
7390             }
7391             if (cps->other->sendTime) {
7392                 SendTimeRemaining(cps->other,
7393                                   cps->other->twoMachinesColor[0] == 'w');
7394             }
7395             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7396             if (firstMove && !bookHit) {
7397                 firstMove = FALSE;
7398                 if (cps->other->useColors) {
7399                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7400                 }
7401                 SendToProgram("go\n", cps->other);
7402             }
7403             cps->other->maybeThinking = TRUE;
7404         }
7405
7406         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7407
7408         if (!pausing && appData.ringBellAfterMoves) {
7409             RingBell();
7410         }
7411
7412         /*
7413          * Reenable menu items that were disabled while
7414          * machine was thinking
7415          */
7416         if (gameMode != TwoMachinesPlay)
7417             SetUserThinkingEnables();
7418
7419         // [HGM] book: after book hit opponent has received move and is now in force mode
7420         // force the book reply into it, and then fake that it outputted this move by jumping
7421         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7422         if(bookHit) {
7423                 static char bookMove[MSG_SIZ]; // a bit generous?
7424
7425                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7426                 strcat(bookMove, bookHit);
7427                 message = bookMove;
7428                 cps = cps->other;
7429                 programStats.nodes = programStats.depth = programStats.time =
7430                 programStats.score = programStats.got_only_move = 0;
7431                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7432
7433                 if(cps->lastPing != cps->lastPong) {
7434                     savedMessage = message; // args for deferred call
7435                     savedState = cps;
7436                     ScheduleDelayedEvent(DeferredBookMove, 10);
7437                     return;
7438                 }
7439                 goto FakeBookMove;
7440         }
7441
7442         return;
7443     }
7444
7445     /* Set special modes for chess engines.  Later something general
7446      *  could be added here; for now there is just one kludge feature,
7447      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7448      *  when "xboard" is given as an interactive command.
7449      */
7450     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7451         cps->useSigint = FALSE;
7452         cps->useSigterm = FALSE;
7453     }
7454     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7455       ParseFeatures(message+8, cps);
7456       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7457     }
7458
7459     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7460      * want this, I was asked to put it in, and obliged.
7461      */
7462     if (!strncmp(message, "setboard ", 9)) {
7463         Board initial_position;
7464
7465         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7466
7467         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7468             DisplayError(_("Bad FEN received from engine"), 0);
7469             return ;
7470         } else {
7471            Reset(TRUE, FALSE);
7472            CopyBoard(boards[0], initial_position);
7473            initialRulePlies = FENrulePlies;
7474            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7475            else gameMode = MachinePlaysBlack;
7476            DrawPosition(FALSE, boards[currentMove]);
7477         }
7478         return;
7479     }
7480
7481     /*
7482      * Look for communication commands
7483      */
7484     if (!strncmp(message, "telluser ", 9)) {
7485         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7486         DisplayNote(message + 9);
7487         return;
7488     }
7489     if (!strncmp(message, "tellusererror ", 14)) {
7490         cps->userError = 1;
7491         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7492         DisplayError(message + 14, 0);
7493         return;
7494     }
7495     if (!strncmp(message, "tellopponent ", 13)) {
7496       if (appData.icsActive) {
7497         if (loggedOn) {
7498           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7499           SendToICS(buf1);
7500         }
7501       } else {
7502         DisplayNote(message + 13);
7503       }
7504       return;
7505     }
7506     if (!strncmp(message, "tellothers ", 11)) {
7507       if (appData.icsActive) {
7508         if (loggedOn) {
7509           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7510           SendToICS(buf1);
7511         }
7512       }
7513       return;
7514     }
7515     if (!strncmp(message, "tellall ", 8)) {
7516       if (appData.icsActive) {
7517         if (loggedOn) {
7518           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7519           SendToICS(buf1);
7520         }
7521       } else {
7522         DisplayNote(message + 8);
7523       }
7524       return;
7525     }
7526     if (strncmp(message, "warning", 7) == 0) {
7527         /* Undocumented feature, use tellusererror in new code */
7528         DisplayError(message, 0);
7529         return;
7530     }
7531     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7532         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7533         strcat(realname, " query");
7534         AskQuestion(realname, buf2, buf1, cps->pr);
7535         return;
7536     }
7537     /* Commands from the engine directly to ICS.  We don't allow these to be
7538      *  sent until we are logged on. Crafty kibitzes have been known to
7539      *  interfere with the login process.
7540      */
7541     if (loggedOn) {
7542         if (!strncmp(message, "tellics ", 8)) {
7543             SendToICS(message + 8);
7544             SendToICS("\n");
7545             return;
7546         }
7547         if (!strncmp(message, "tellicsnoalias ", 15)) {
7548             SendToICS(ics_prefix);
7549             SendToICS(message + 15);
7550             SendToICS("\n");
7551             return;
7552         }
7553         /* The following are for backward compatibility only */
7554         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7555             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7556             SendToICS(ics_prefix);
7557             SendToICS(message);
7558             SendToICS("\n");
7559             return;
7560         }
7561     }
7562     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7563         return;
7564     }
7565     /*
7566      * If the move is illegal, cancel it and redraw the board.
7567      * Also deal with other error cases.  Matching is rather loose
7568      * here to accommodate engines written before the spec.
7569      */
7570     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7571         strncmp(message, "Error", 5) == 0) {
7572         if (StrStr(message, "name") ||
7573             StrStr(message, "rating") || StrStr(message, "?") ||
7574             StrStr(message, "result") || StrStr(message, "board") ||
7575             StrStr(message, "bk") || StrStr(message, "computer") ||
7576             StrStr(message, "variant") || StrStr(message, "hint") ||
7577             StrStr(message, "random") || StrStr(message, "depth") ||
7578             StrStr(message, "accepted")) {
7579             return;
7580         }
7581         if (StrStr(message, "protover")) {
7582           /* Program is responding to input, so it's apparently done
7583              initializing, and this error message indicates it is
7584              protocol version 1.  So we don't need to wait any longer
7585              for it to initialize and send feature commands. */
7586           FeatureDone(cps, 1);
7587           cps->protocolVersion = 1;
7588           return;
7589         }
7590         cps->maybeThinking = FALSE;
7591
7592         if (StrStr(message, "draw")) {
7593             /* Program doesn't have "draw" command */
7594             cps->sendDrawOffers = 0;
7595             return;
7596         }
7597         if (cps->sendTime != 1 &&
7598             (StrStr(message, "time") || StrStr(message, "otim"))) {
7599           /* Program apparently doesn't have "time" or "otim" command */
7600           cps->sendTime = 0;
7601           return;
7602         }
7603         if (StrStr(message, "analyze")) {
7604             cps->analysisSupport = FALSE;
7605             cps->analyzing = FALSE;
7606             Reset(FALSE, TRUE);
7607             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7608             DisplayError(buf2, 0);
7609             return;
7610         }
7611         if (StrStr(message, "(no matching move)st")) {
7612           /* Special kludge for GNU Chess 4 only */
7613           cps->stKludge = TRUE;
7614           SendTimeControl(cps, movesPerSession, timeControl,
7615                           timeIncrement, appData.searchDepth,
7616                           searchTime);
7617           return;
7618         }
7619         if (StrStr(message, "(no matching move)sd")) {
7620           /* Special kludge for GNU Chess 4 only */
7621           cps->sdKludge = TRUE;
7622           SendTimeControl(cps, movesPerSession, timeControl,
7623                           timeIncrement, appData.searchDepth,
7624                           searchTime);
7625           return;
7626         }
7627         if (!StrStr(message, "llegal")) {
7628             return;
7629         }
7630         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7631             gameMode == IcsIdle) return;
7632         if (forwardMostMove <= backwardMostMove) return;
7633         if (pausing) PauseEvent();
7634       if(appData.forceIllegal) {
7635             // [HGM] illegal: machine refused move; force position after move into it
7636           SendToProgram("force\n", cps);
7637           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7638                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7639                 // when black is to move, while there might be nothing on a2 or black
7640                 // might already have the move. So send the board as if white has the move.
7641                 // But first we must change the stm of the engine, as it refused the last move
7642                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7643                 if(WhiteOnMove(forwardMostMove)) {
7644                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7645                     SendBoard(cps, forwardMostMove); // kludgeless board
7646                 } else {
7647                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7648                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7649                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7650                 }
7651           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7652             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7653                  gameMode == TwoMachinesPlay)
7654               SendToProgram("go\n", cps);
7655             return;
7656       } else
7657         if (gameMode == PlayFromGameFile) {
7658             /* Stop reading this game file */
7659             gameMode = EditGame;
7660             ModeHighlight();
7661         }
7662         currentMove = forwardMostMove-1;
7663         DisplayMove(currentMove-1); /* before DisplayMoveError */
7664         SwitchClocks(forwardMostMove-1); // [HGM] race
7665         DisplayBothClocks();
7666         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7667                 parseList[currentMove], cps->which);
7668         DisplayMoveError(buf1);
7669         DrawPosition(FALSE, boards[currentMove]);
7670
7671         /* [HGM] illegal-move claim should forfeit game when Xboard */
7672         /* only passes fully legal moves                            */
7673         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7674             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7675                                 "False illegal-move claim", GE_XBOARD );
7676         }
7677         return;
7678     }
7679     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7680         /* Program has a broken "time" command that
7681            outputs a string not ending in newline.
7682            Don't use it. */
7683         cps->sendTime = 0;
7684     }
7685
7686     /*
7687      * If chess program startup fails, exit with an error message.
7688      * Attempts to recover here are futile.
7689      */
7690     if ((StrStr(message, "unknown host") != NULL)
7691         || (StrStr(message, "No remote directory") != NULL)
7692         || (StrStr(message, "not found") != NULL)
7693         || (StrStr(message, "No such file") != NULL)
7694         || (StrStr(message, "can't alloc") != NULL)
7695         || (StrStr(message, "Permission denied") != NULL)) {
7696
7697         cps->maybeThinking = FALSE;
7698         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7699                 cps->which, cps->program, cps->host, message);
7700         RemoveInputSource(cps->isr);
7701         DisplayFatalError(buf1, 0, 1);
7702         return;
7703     }
7704
7705     /*
7706      * Look for hint output
7707      */
7708     if (sscanf(message, "Hint: %s", buf1) == 1) {
7709         if (cps == &first && hintRequested) {
7710             hintRequested = FALSE;
7711             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7712                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7713                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7714                                     PosFlags(forwardMostMove),
7715                                     fromY, fromX, toY, toX, promoChar, buf1);
7716                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7717                 DisplayInformation(buf2);
7718             } else {
7719                 /* Hint move could not be parsed!? */
7720               snprintf(buf2, sizeof(buf2),
7721                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7722                         buf1, cps->which);
7723                 DisplayError(buf2, 0);
7724             }
7725         } else {
7726           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7727         }
7728         return;
7729     }
7730
7731     /*
7732      * Ignore other messages if game is not in progress
7733      */
7734     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7735         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7736
7737     /*
7738      * look for win, lose, draw, or draw offer
7739      */
7740     if (strncmp(message, "1-0", 3) == 0) {
7741         char *p, *q, *r = "";
7742         p = strchr(message, '{');
7743         if (p) {
7744             q = strchr(p, '}');
7745             if (q) {
7746                 *q = NULLCHAR;
7747                 r = p + 1;
7748             }
7749         }
7750         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7751         return;
7752     } else if (strncmp(message, "0-1", 3) == 0) {
7753         char *p, *q, *r = "";
7754         p = strchr(message, '{');
7755         if (p) {
7756             q = strchr(p, '}');
7757             if (q) {
7758                 *q = NULLCHAR;
7759                 r = p + 1;
7760             }
7761         }
7762         /* Kludge for Arasan 4.1 bug */
7763         if (strcmp(r, "Black resigns") == 0) {
7764             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7765             return;
7766         }
7767         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7768         return;
7769     } else if (strncmp(message, "1/2", 3) == 0) {
7770         char *p, *q, *r = "";
7771         p = strchr(message, '{');
7772         if (p) {
7773             q = strchr(p, '}');
7774             if (q) {
7775                 *q = NULLCHAR;
7776                 r = p + 1;
7777             }
7778         }
7779
7780         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7781         return;
7782
7783     } else if (strncmp(message, "White resign", 12) == 0) {
7784         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7785         return;
7786     } else if (strncmp(message, "Black resign", 12) == 0) {
7787         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7788         return;
7789     } else if (strncmp(message, "White matches", 13) == 0 ||
7790                strncmp(message, "Black matches", 13) == 0   ) {
7791         /* [HGM] ignore GNUShogi noises */
7792         return;
7793     } else if (strncmp(message, "White", 5) == 0 &&
7794                message[5] != '(' &&
7795                StrStr(message, "Black") == NULL) {
7796         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7797         return;
7798     } else if (strncmp(message, "Black", 5) == 0 &&
7799                message[5] != '(') {
7800         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7801         return;
7802     } else if (strcmp(message, "resign") == 0 ||
7803                strcmp(message, "computer resigns") == 0) {
7804         switch (gameMode) {
7805           case MachinePlaysBlack:
7806           case IcsPlayingBlack:
7807             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7808             break;
7809           case MachinePlaysWhite:
7810           case IcsPlayingWhite:
7811             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7812             break;
7813           case TwoMachinesPlay:
7814             if (cps->twoMachinesColor[0] == 'w')
7815               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7816             else
7817               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7818             break;
7819           default:
7820             /* can't happen */
7821             break;
7822         }
7823         return;
7824     } else if (strncmp(message, "opponent mates", 14) == 0) {
7825         switch (gameMode) {
7826           case MachinePlaysBlack:
7827           case IcsPlayingBlack:
7828             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7829             break;
7830           case MachinePlaysWhite:
7831           case IcsPlayingWhite:
7832             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7833             break;
7834           case TwoMachinesPlay:
7835             if (cps->twoMachinesColor[0] == 'w')
7836               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7837             else
7838               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7839             break;
7840           default:
7841             /* can't happen */
7842             break;
7843         }
7844         return;
7845     } else if (strncmp(message, "computer mates", 14) == 0) {
7846         switch (gameMode) {
7847           case MachinePlaysBlack:
7848           case IcsPlayingBlack:
7849             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7850             break;
7851           case MachinePlaysWhite:
7852           case IcsPlayingWhite:
7853             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7854             break;
7855           case TwoMachinesPlay:
7856             if (cps->twoMachinesColor[0] == 'w')
7857               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7858             else
7859               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7860             break;
7861           default:
7862             /* can't happen */
7863             break;
7864         }
7865         return;
7866     } else if (strncmp(message, "checkmate", 9) == 0) {
7867         if (WhiteOnMove(forwardMostMove)) {
7868             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7869         } else {
7870             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7871         }
7872         return;
7873     } else if (strstr(message, "Draw") != NULL ||
7874                strstr(message, "game is a draw") != NULL) {
7875         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7876         return;
7877     } else if (strstr(message, "offer") != NULL &&
7878                strstr(message, "draw") != NULL) {
7879 #if ZIPPY
7880         if (appData.zippyPlay && first.initDone) {
7881             /* Relay offer to ICS */
7882             SendToICS(ics_prefix);
7883             SendToICS("draw\n");
7884         }
7885 #endif
7886         cps->offeredDraw = 2; /* valid until this engine moves twice */
7887         if (gameMode == TwoMachinesPlay) {
7888             if (cps->other->offeredDraw) {
7889                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7890             /* [HGM] in two-machine mode we delay relaying draw offer      */
7891             /* until after we also have move, to see if it is really claim */
7892             }
7893         } else if (gameMode == MachinePlaysWhite ||
7894                    gameMode == MachinePlaysBlack) {
7895           if (userOfferedDraw) {
7896             DisplayInformation(_("Machine accepts your draw offer"));
7897             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7898           } else {
7899             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7900           }
7901         }
7902     }
7903
7904
7905     /*
7906      * Look for thinking output
7907      */
7908     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7909           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7910                                 ) {
7911         int plylev, mvleft, mvtot, curscore, time;
7912         char mvname[MOVE_LEN];
7913         u64 nodes; // [DM]
7914         char plyext;
7915         int ignore = FALSE;
7916         int prefixHint = FALSE;
7917         mvname[0] = NULLCHAR;
7918
7919         switch (gameMode) {
7920           case MachinePlaysBlack:
7921           case IcsPlayingBlack:
7922             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7923             break;
7924           case MachinePlaysWhite:
7925           case IcsPlayingWhite:
7926             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7927             break;
7928           case AnalyzeMode:
7929           case AnalyzeFile:
7930             break;
7931           case IcsObserving: /* [DM] icsEngineAnalyze */
7932             if (!appData.icsEngineAnalyze) ignore = TRUE;
7933             break;
7934           case TwoMachinesPlay:
7935             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7936                 ignore = TRUE;
7937             }
7938             break;
7939           default:
7940             ignore = TRUE;
7941             break;
7942         }
7943
7944         if (!ignore) {
7945             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7946             buf1[0] = NULLCHAR;
7947             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7948                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7949
7950                 if (plyext != ' ' && plyext != '\t') {
7951                     time *= 100;
7952                 }
7953
7954                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7955                 if( cps->scoreIsAbsolute &&
7956                     ( gameMode == MachinePlaysBlack ||
7957                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7958                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7959                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7960                      !WhiteOnMove(currentMove)
7961                     ) )
7962                 {
7963                     curscore = -curscore;
7964                 }
7965
7966
7967                 tempStats.depth = plylev;
7968                 tempStats.nodes = nodes;
7969                 tempStats.time = time;
7970                 tempStats.score = curscore;
7971                 tempStats.got_only_move = 0;
7972
7973                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7974                         int ticklen;
7975
7976                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7977                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7978                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7979                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7980                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7981                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7982                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7983                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7984                 }
7985
7986                 /* Buffer overflow protection */
7987                 if (buf1[0] != NULLCHAR) {
7988                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7989                         && appData.debugMode) {
7990                         fprintf(debugFP,
7991                                 "PV is too long; using the first %u bytes.\n",
7992                                 (unsigned) sizeof(tempStats.movelist) - 1);
7993                     }
7994
7995                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
7996                 } else {
7997                     sprintf(tempStats.movelist, " no PV\n");
7998                 }
7999
8000                 if (tempStats.seen_stat) {
8001                     tempStats.ok_to_send = 1;
8002                 }
8003
8004                 if (strchr(tempStats.movelist, '(') != NULL) {
8005                     tempStats.line_is_book = 1;
8006                     tempStats.nr_moves = 0;
8007                     tempStats.moves_left = 0;
8008                 } else {
8009                     tempStats.line_is_book = 0;
8010                 }
8011
8012                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8013                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8014
8015                 SendProgramStatsToFrontend( cps, &tempStats );
8016
8017                 /*
8018                     [AS] Protect the thinkOutput buffer from overflow... this
8019                     is only useful if buf1 hasn't overflowed first!
8020                 */
8021                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8022                          plylev,
8023                          (gameMode == TwoMachinesPlay ?
8024                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8025                          ((double) curscore) / 100.0,
8026                          prefixHint ? lastHint : "",
8027                          prefixHint ? " " : "" );
8028
8029                 if( buf1[0] != NULLCHAR ) {
8030                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8031
8032                     if( strlen(buf1) > max_len ) {
8033                         if( appData.debugMode) {
8034                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8035                         }
8036                         buf1[max_len+1] = '\0';
8037                     }
8038
8039                     strcat( thinkOutput, buf1 );
8040                 }
8041
8042                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8043                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8044                     DisplayMove(currentMove - 1);
8045                 }
8046                 return;
8047
8048             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8049                 /* crafty (9.25+) says "(only move) <move>"
8050                  * if there is only 1 legal move
8051                  */
8052                 sscanf(p, "(only move) %s", buf1);
8053                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8054                 sprintf(programStats.movelist, "%s (only move)", buf1);
8055                 programStats.depth = 1;
8056                 programStats.nr_moves = 1;
8057                 programStats.moves_left = 1;
8058                 programStats.nodes = 1;
8059                 programStats.time = 1;
8060                 programStats.got_only_move = 1;
8061
8062                 /* Not really, but we also use this member to
8063                    mean "line isn't going to change" (Crafty
8064                    isn't searching, so stats won't change) */
8065                 programStats.line_is_book = 1;
8066
8067                 SendProgramStatsToFrontend( cps, &programStats );
8068
8069                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8070                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8071                     DisplayMove(currentMove - 1);
8072                 }
8073                 return;
8074             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8075                               &time, &nodes, &plylev, &mvleft,
8076                               &mvtot, mvname) >= 5) {
8077                 /* The stat01: line is from Crafty (9.29+) in response
8078                    to the "." command */
8079                 programStats.seen_stat = 1;
8080                 cps->maybeThinking = TRUE;
8081
8082                 if (programStats.got_only_move || !appData.periodicUpdates)
8083                   return;
8084
8085                 programStats.depth = plylev;
8086                 programStats.time = time;
8087                 programStats.nodes = nodes;
8088                 programStats.moves_left = mvleft;
8089                 programStats.nr_moves = mvtot;
8090                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8091                 programStats.ok_to_send = 1;
8092                 programStats.movelist[0] = '\0';
8093
8094                 SendProgramStatsToFrontend( cps, &programStats );
8095
8096                 return;
8097
8098             } else if (strncmp(message,"++",2) == 0) {
8099                 /* Crafty 9.29+ outputs this */
8100                 programStats.got_fail = 2;
8101                 return;
8102
8103             } else if (strncmp(message,"--",2) == 0) {
8104                 /* Crafty 9.29+ outputs this */
8105                 programStats.got_fail = 1;
8106                 return;
8107
8108             } else if (thinkOutput[0] != NULLCHAR &&
8109                        strncmp(message, "    ", 4) == 0) {
8110                 unsigned message_len;
8111
8112                 p = message;
8113                 while (*p && *p == ' ') p++;
8114
8115                 message_len = strlen( p );
8116
8117                 /* [AS] Avoid buffer overflow */
8118                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8119                     strcat(thinkOutput, " ");
8120                     strcat(thinkOutput, p);
8121                 }
8122
8123                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8124                     strcat(programStats.movelist, " ");
8125                     strcat(programStats.movelist, p);
8126                 }
8127
8128                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8129                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8130                     DisplayMove(currentMove - 1);
8131                 }
8132                 return;
8133             }
8134         }
8135         else {
8136             buf1[0] = NULLCHAR;
8137
8138             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8139                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8140             {
8141                 ChessProgramStats cpstats;
8142
8143                 if (plyext != ' ' && plyext != '\t') {
8144                     time *= 100;
8145                 }
8146
8147                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8148                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8149                     curscore = -curscore;
8150                 }
8151
8152                 cpstats.depth = plylev;
8153                 cpstats.nodes = nodes;
8154                 cpstats.time = time;
8155                 cpstats.score = curscore;
8156                 cpstats.got_only_move = 0;
8157                 cpstats.movelist[0] = '\0';
8158
8159                 if (buf1[0] != NULLCHAR) {
8160                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8161                 }
8162
8163                 cpstats.ok_to_send = 0;
8164                 cpstats.line_is_book = 0;
8165                 cpstats.nr_moves = 0;
8166                 cpstats.moves_left = 0;
8167
8168                 SendProgramStatsToFrontend( cps, &cpstats );
8169             }
8170         }
8171     }
8172 }
8173
8174
8175 /* Parse a game score from the character string "game", and
8176    record it as the history of the current game.  The game
8177    score is NOT assumed to start from the standard position.
8178    The display is not updated in any way.
8179    */
8180 void
8181 ParseGameHistory(game)
8182      char *game;
8183 {
8184     ChessMove moveType;
8185     int fromX, fromY, toX, toY, boardIndex;
8186     char promoChar;
8187     char *p, *q;
8188     char buf[MSG_SIZ];
8189
8190     if (appData.debugMode)
8191       fprintf(debugFP, "Parsing game history: %s\n", game);
8192
8193     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8194     gameInfo.site = StrSave(appData.icsHost);
8195     gameInfo.date = PGNDate();
8196     gameInfo.round = StrSave("-");
8197
8198     /* Parse out names of players */
8199     while (*game == ' ') game++;
8200     p = buf;
8201     while (*game != ' ') *p++ = *game++;
8202     *p = NULLCHAR;
8203     gameInfo.white = StrSave(buf);
8204     while (*game == ' ') game++;
8205     p = buf;
8206     while (*game != ' ' && *game != '\n') *p++ = *game++;
8207     *p = NULLCHAR;
8208     gameInfo.black = StrSave(buf);
8209
8210     /* Parse moves */
8211     boardIndex = blackPlaysFirst ? 1 : 0;
8212     yynewstr(game);
8213     for (;;) {
8214         yyboardindex = boardIndex;
8215         moveType = (ChessMove) yylex();
8216         switch (moveType) {
8217           case IllegalMove:             /* maybe suicide chess, etc. */
8218   if (appData.debugMode) {
8219     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8220     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8221     setbuf(debugFP, NULL);
8222   }
8223           case WhitePromotion:
8224           case BlackPromotion:
8225           case WhiteNonPromotion:
8226           case BlackNonPromotion:
8227           case NormalMove:
8228           case WhiteCapturesEnPassant:
8229           case BlackCapturesEnPassant:
8230           case WhiteKingSideCastle:
8231           case WhiteQueenSideCastle:
8232           case BlackKingSideCastle:
8233           case BlackQueenSideCastle:
8234           case WhiteKingSideCastleWild:
8235           case WhiteQueenSideCastleWild:
8236           case BlackKingSideCastleWild:
8237           case BlackQueenSideCastleWild:
8238           /* PUSH Fabien */
8239           case WhiteHSideCastleFR:
8240           case WhiteASideCastleFR:
8241           case BlackHSideCastleFR:
8242           case BlackASideCastleFR:
8243           /* POP Fabien */
8244             fromX = currentMoveString[0] - AAA;
8245             fromY = currentMoveString[1] - ONE;
8246             toX = currentMoveString[2] - AAA;
8247             toY = currentMoveString[3] - ONE;
8248             promoChar = currentMoveString[4];
8249             break;
8250           case WhiteDrop:
8251           case BlackDrop:
8252             fromX = moveType == WhiteDrop ?
8253               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8254             (int) CharToPiece(ToLower(currentMoveString[0]));
8255             fromY = DROP_RANK;
8256             toX = currentMoveString[2] - AAA;
8257             toY = currentMoveString[3] - ONE;
8258             promoChar = NULLCHAR;
8259             break;
8260           case AmbiguousMove:
8261             /* bug? */
8262             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8263   if (appData.debugMode) {
8264     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8265     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8266     setbuf(debugFP, NULL);
8267   }
8268             DisplayError(buf, 0);
8269             return;
8270           case ImpossibleMove:
8271             /* bug? */
8272             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8273   if (appData.debugMode) {
8274     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8275     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8276     setbuf(debugFP, NULL);
8277   }
8278             DisplayError(buf, 0);
8279             return;
8280           case EndOfFile:
8281             if (boardIndex < backwardMostMove) {
8282                 /* Oops, gap.  How did that happen? */
8283                 DisplayError(_("Gap in move list"), 0);
8284                 return;
8285             }
8286             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8287             if (boardIndex > forwardMostMove) {
8288                 forwardMostMove = boardIndex;
8289             }
8290             return;
8291           case ElapsedTime:
8292             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8293                 strcat(parseList[boardIndex-1], " ");
8294                 strcat(parseList[boardIndex-1], yy_text);
8295             }
8296             continue;
8297           case Comment:
8298           case PGNTag:
8299           case NAG:
8300           default:
8301             /* ignore */
8302             continue;
8303           case WhiteWins:
8304           case BlackWins:
8305           case GameIsDrawn:
8306           case GameUnfinished:
8307             if (gameMode == IcsExamining) {
8308                 if (boardIndex < backwardMostMove) {
8309                     /* Oops, gap.  How did that happen? */
8310                     return;
8311                 }
8312                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8313                 return;
8314             }
8315             gameInfo.result = moveType;
8316             p = strchr(yy_text, '{');
8317             if (p == NULL) p = strchr(yy_text, '(');
8318             if (p == NULL) {
8319                 p = yy_text;
8320                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8321             } else {
8322                 q = strchr(p, *p == '{' ? '}' : ')');
8323                 if (q != NULL) *q = NULLCHAR;
8324                 p++;
8325             }
8326             gameInfo.resultDetails = StrSave(p);
8327             continue;
8328         }
8329         if (boardIndex >= forwardMostMove &&
8330             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8331             backwardMostMove = blackPlaysFirst ? 1 : 0;
8332             return;
8333         }
8334         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8335                                  fromY, fromX, toY, toX, promoChar,
8336                                  parseList[boardIndex]);
8337         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8338         /* currentMoveString is set as a side-effect of yylex */
8339         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8340         strcat(moveList[boardIndex], "\n");
8341         boardIndex++;
8342         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8343         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8344           case MT_NONE:
8345           case MT_STALEMATE:
8346           default:
8347             break;
8348           case MT_CHECK:
8349             if(gameInfo.variant != VariantShogi)
8350                 strcat(parseList[boardIndex - 1], "+");
8351             break;
8352           case MT_CHECKMATE:
8353           case MT_STAINMATE:
8354             strcat(parseList[boardIndex - 1], "#");
8355             break;
8356         }
8357     }
8358 }
8359
8360
8361 /* Apply a move to the given board  */
8362 void
8363 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8364      int fromX, fromY, toX, toY;
8365      int promoChar;
8366      Board board;
8367 {
8368   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8369   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8370
8371     /* [HGM] compute & store e.p. status and castling rights for new position */
8372     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8373
8374       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8375       oldEP = (signed char)board[EP_STATUS];
8376       board[EP_STATUS] = EP_NONE;
8377
8378       if( board[toY][toX] != EmptySquare )
8379            board[EP_STATUS] = EP_CAPTURE;
8380
8381   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8382   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8383        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8384
8385   if (fromY == DROP_RANK) {
8386         /* must be first */
8387         piece = board[toY][toX] = (ChessSquare) fromX;
8388   } else {
8389       int i;
8390
8391       if( board[fromY][fromX] == WhitePawn ) {
8392            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8393                board[EP_STATUS] = EP_PAWN_MOVE;
8394            if( toY-fromY==2) {
8395                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8396                         gameInfo.variant != VariantBerolina || toX < fromX)
8397                       board[EP_STATUS] = toX | berolina;
8398                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8399                         gameInfo.variant != VariantBerolina || toX > fromX)
8400                       board[EP_STATUS] = toX;
8401            }
8402       } else
8403       if( board[fromY][fromX] == BlackPawn ) {
8404            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8405                board[EP_STATUS] = EP_PAWN_MOVE;
8406            if( toY-fromY== -2) {
8407                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8408                         gameInfo.variant != VariantBerolina || toX < fromX)
8409                       board[EP_STATUS] = toX | berolina;
8410                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8411                         gameInfo.variant != VariantBerolina || toX > fromX)
8412                       board[EP_STATUS] = toX;
8413            }
8414        }
8415
8416        for(i=0; i<nrCastlingRights; i++) {
8417            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8418               board[CASTLING][i] == toX   && castlingRank[i] == toY
8419              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8420        }
8421
8422      if (fromX == toX && fromY == toY) return;
8423
8424      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8425      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8426      if(gameInfo.variant == VariantKnightmate)
8427          king += (int) WhiteUnicorn - (int) WhiteKing;
8428
8429     /* Code added by Tord: */
8430     /* FRC castling assumed when king captures friendly rook. */
8431     if (board[fromY][fromX] == WhiteKing &&
8432              board[toY][toX] == WhiteRook) {
8433       board[fromY][fromX] = EmptySquare;
8434       board[toY][toX] = EmptySquare;
8435       if(toX > fromX) {
8436         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8437       } else {
8438         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8439       }
8440     } else if (board[fromY][fromX] == BlackKing &&
8441                board[toY][toX] == BlackRook) {
8442       board[fromY][fromX] = EmptySquare;
8443       board[toY][toX] = EmptySquare;
8444       if(toX > fromX) {
8445         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8446       } else {
8447         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8448       }
8449     /* End of code added by Tord */
8450
8451     } else if (board[fromY][fromX] == king
8452         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8453         && toY == fromY && toX > fromX+1) {
8454         board[fromY][fromX] = EmptySquare;
8455         board[toY][toX] = king;
8456         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8457         board[fromY][BOARD_RGHT-1] = EmptySquare;
8458     } else if (board[fromY][fromX] == king
8459         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8460                && toY == fromY && toX < fromX-1) {
8461         board[fromY][fromX] = EmptySquare;
8462         board[toY][toX] = king;
8463         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8464         board[fromY][BOARD_LEFT] = EmptySquare;
8465     } else if (board[fromY][fromX] == WhitePawn
8466                && toY >= BOARD_HEIGHT-promoRank
8467                && gameInfo.variant != VariantXiangqi
8468                ) {
8469         /* white pawn promotion */
8470         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8471         if (board[toY][toX] == EmptySquare) {
8472             board[toY][toX] = WhiteQueen;
8473         }
8474         if(gameInfo.variant==VariantBughouse ||
8475            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8476             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8477         board[fromY][fromX] = EmptySquare;
8478     } else if ((fromY == BOARD_HEIGHT-4)
8479                && (toX != fromX)
8480                && gameInfo.variant != VariantXiangqi
8481                && gameInfo.variant != VariantBerolina
8482                && (board[fromY][fromX] == WhitePawn)
8483                && (board[toY][toX] == EmptySquare)) {
8484         board[fromY][fromX] = EmptySquare;
8485         board[toY][toX] = WhitePawn;
8486         captured = board[toY - 1][toX];
8487         board[toY - 1][toX] = EmptySquare;
8488     } else if ((fromY == BOARD_HEIGHT-4)
8489                && (toX == fromX)
8490                && gameInfo.variant == VariantBerolina
8491                && (board[fromY][fromX] == WhitePawn)
8492                && (board[toY][toX] == EmptySquare)) {
8493         board[fromY][fromX] = EmptySquare;
8494         board[toY][toX] = WhitePawn;
8495         if(oldEP & EP_BEROLIN_A) {
8496                 captured = board[fromY][fromX-1];
8497                 board[fromY][fromX-1] = EmptySquare;
8498         }else{  captured = board[fromY][fromX+1];
8499                 board[fromY][fromX+1] = EmptySquare;
8500         }
8501     } else if (board[fromY][fromX] == king
8502         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8503                && toY == fromY && toX > fromX+1) {
8504         board[fromY][fromX] = EmptySquare;
8505         board[toY][toX] = king;
8506         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8507         board[fromY][BOARD_RGHT-1] = EmptySquare;
8508     } else if (board[fromY][fromX] == king
8509         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8510                && toY == fromY && toX < fromX-1) {
8511         board[fromY][fromX] = EmptySquare;
8512         board[toY][toX] = king;
8513         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8514         board[fromY][BOARD_LEFT] = EmptySquare;
8515     } else if (fromY == 7 && fromX == 3
8516                && board[fromY][fromX] == BlackKing
8517                && toY == 7 && toX == 5) {
8518         board[fromY][fromX] = EmptySquare;
8519         board[toY][toX] = BlackKing;
8520         board[fromY][7] = EmptySquare;
8521         board[toY][4] = BlackRook;
8522     } else if (fromY == 7 && fromX == 3
8523                && board[fromY][fromX] == BlackKing
8524                && toY == 7 && toX == 1) {
8525         board[fromY][fromX] = EmptySquare;
8526         board[toY][toX] = BlackKing;
8527         board[fromY][0] = EmptySquare;
8528         board[toY][2] = BlackRook;
8529     } else if (board[fromY][fromX] == BlackPawn
8530                && toY < promoRank
8531                && gameInfo.variant != VariantXiangqi
8532                ) {
8533         /* black pawn promotion */
8534         board[toY][toX] = CharToPiece(ToLower(promoChar));
8535         if (board[toY][toX] == EmptySquare) {
8536             board[toY][toX] = BlackQueen;
8537         }
8538         if(gameInfo.variant==VariantBughouse ||
8539            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8540             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8541         board[fromY][fromX] = EmptySquare;
8542     } else if ((fromY == 3)
8543                && (toX != fromX)
8544                && gameInfo.variant != VariantXiangqi
8545                && gameInfo.variant != VariantBerolina
8546                && (board[fromY][fromX] == BlackPawn)
8547                && (board[toY][toX] == EmptySquare)) {
8548         board[fromY][fromX] = EmptySquare;
8549         board[toY][toX] = BlackPawn;
8550         captured = board[toY + 1][toX];
8551         board[toY + 1][toX] = EmptySquare;
8552     } else if ((fromY == 3)
8553                && (toX == fromX)
8554                && gameInfo.variant == VariantBerolina
8555                && (board[fromY][fromX] == BlackPawn)
8556                && (board[toY][toX] == EmptySquare)) {
8557         board[fromY][fromX] = EmptySquare;
8558         board[toY][toX] = BlackPawn;
8559         if(oldEP & EP_BEROLIN_A) {
8560                 captured = board[fromY][fromX-1];
8561                 board[fromY][fromX-1] = EmptySquare;
8562         }else{  captured = board[fromY][fromX+1];
8563                 board[fromY][fromX+1] = EmptySquare;
8564         }
8565     } else {
8566         board[toY][toX] = board[fromY][fromX];
8567         board[fromY][fromX] = EmptySquare;
8568     }
8569   }
8570
8571     if (gameInfo.holdingsWidth != 0) {
8572
8573       /* !!A lot more code needs to be written to support holdings  */
8574       /* [HGM] OK, so I have written it. Holdings are stored in the */
8575       /* penultimate board files, so they are automaticlly stored   */
8576       /* in the game history.                                       */
8577       if (fromY == DROP_RANK) {
8578         /* Delete from holdings, by decreasing count */
8579         /* and erasing image if necessary            */
8580         p = (int) fromX;
8581         if(p < (int) BlackPawn) { /* white drop */
8582              p -= (int)WhitePawn;
8583                  p = PieceToNumber((ChessSquare)p);
8584              if(p >= gameInfo.holdingsSize) p = 0;
8585              if(--board[p][BOARD_WIDTH-2] <= 0)
8586                   board[p][BOARD_WIDTH-1] = EmptySquare;
8587              if((int)board[p][BOARD_WIDTH-2] < 0)
8588                         board[p][BOARD_WIDTH-2] = 0;
8589         } else {                  /* black drop */
8590              p -= (int)BlackPawn;
8591                  p = PieceToNumber((ChessSquare)p);
8592              if(p >= gameInfo.holdingsSize) p = 0;
8593              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8594                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8595              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8596                         board[BOARD_HEIGHT-1-p][1] = 0;
8597         }
8598       }
8599       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8600           && gameInfo.variant != VariantBughouse        ) {
8601         /* [HGM] holdings: Add to holdings, if holdings exist */
8602         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8603                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8604                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8605         }
8606         p = (int) captured;
8607         if (p >= (int) BlackPawn) {
8608           p -= (int)BlackPawn;
8609           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8610                   /* in Shogi restore piece to its original  first */
8611                   captured = (ChessSquare) (DEMOTED captured);
8612                   p = DEMOTED p;
8613           }
8614           p = PieceToNumber((ChessSquare)p);
8615           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8616           board[p][BOARD_WIDTH-2]++;
8617           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8618         } else {
8619           p -= (int)WhitePawn;
8620           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8621                   captured = (ChessSquare) (DEMOTED captured);
8622                   p = DEMOTED p;
8623           }
8624           p = PieceToNumber((ChessSquare)p);
8625           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8626           board[BOARD_HEIGHT-1-p][1]++;
8627           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8628         }
8629       }
8630     } else if (gameInfo.variant == VariantAtomic) {
8631       if (captured != EmptySquare) {
8632         int y, x;
8633         for (y = toY-1; y <= toY+1; y++) {
8634           for (x = toX-1; x <= toX+1; x++) {
8635             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8636                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8637               board[y][x] = EmptySquare;
8638             }
8639           }
8640         }
8641         board[toY][toX] = EmptySquare;
8642       }
8643     }
8644     if(promoChar == '+') {
8645         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8646         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8647     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8648         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8649     }
8650     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8651                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8652         // [HGM] superchess: take promotion piece out of holdings
8653         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8654         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8655             if(!--board[k][BOARD_WIDTH-2])
8656                 board[k][BOARD_WIDTH-1] = EmptySquare;
8657         } else {
8658             if(!--board[BOARD_HEIGHT-1-k][1])
8659                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8660         }
8661     }
8662
8663 }
8664
8665 /* Updates forwardMostMove */
8666 void
8667 MakeMove(fromX, fromY, toX, toY, promoChar)
8668      int fromX, fromY, toX, toY;
8669      int promoChar;
8670 {
8671 //    forwardMostMove++; // [HGM] bare: moved downstream
8672
8673     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8674         int timeLeft; static int lastLoadFlag=0; int king, piece;
8675         piece = boards[forwardMostMove][fromY][fromX];
8676         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8677         if(gameInfo.variant == VariantKnightmate)
8678             king += (int) WhiteUnicorn - (int) WhiteKing;
8679         if(forwardMostMove == 0) {
8680             if(blackPlaysFirst)
8681                 fprintf(serverMoves, "%s;", second.tidy);
8682             fprintf(serverMoves, "%s;", first.tidy);
8683             if(!blackPlaysFirst)
8684                 fprintf(serverMoves, "%s;", second.tidy);
8685         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8686         lastLoadFlag = loadFlag;
8687         // print base move
8688         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8689         // print castling suffix
8690         if( toY == fromY && piece == king ) {
8691             if(toX-fromX > 1)
8692                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8693             if(fromX-toX >1)
8694                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8695         }
8696         // e.p. suffix
8697         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8698              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8699              boards[forwardMostMove][toY][toX] == EmptySquare
8700              && fromX != toX && fromY != toY)
8701                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8702         // promotion suffix
8703         if(promoChar != NULLCHAR)
8704                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8705         if(!loadFlag) {
8706             fprintf(serverMoves, "/%d/%d",
8707                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8708             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8709             else                      timeLeft = blackTimeRemaining/1000;
8710             fprintf(serverMoves, "/%d", timeLeft);
8711         }
8712         fflush(serverMoves);
8713     }
8714
8715     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8716       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8717                         0, 1);
8718       return;
8719     }
8720     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8721     if (commentList[forwardMostMove+1] != NULL) {
8722         free(commentList[forwardMostMove+1]);
8723         commentList[forwardMostMove+1] = NULL;
8724     }
8725     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8726     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8727     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8728     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8729     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8730     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8731     gameInfo.result = GameUnfinished;
8732     if (gameInfo.resultDetails != NULL) {
8733         free(gameInfo.resultDetails);
8734         gameInfo.resultDetails = NULL;
8735     }
8736     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8737                               moveList[forwardMostMove - 1]);
8738     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8739                              PosFlags(forwardMostMove - 1),
8740                              fromY, fromX, toY, toX, promoChar,
8741                              parseList[forwardMostMove - 1]);
8742     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8743       case MT_NONE:
8744       case MT_STALEMATE:
8745       default:
8746         break;
8747       case MT_CHECK:
8748         if(gameInfo.variant != VariantShogi)
8749             strcat(parseList[forwardMostMove - 1], "+");
8750         break;
8751       case MT_CHECKMATE:
8752       case MT_STAINMATE:
8753         strcat(parseList[forwardMostMove - 1], "#");
8754         break;
8755     }
8756     if (appData.debugMode) {
8757         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8758     }
8759
8760 }
8761
8762 /* Updates currentMove if not pausing */
8763 void
8764 ShowMove(fromX, fromY, toX, toY)
8765 {
8766     int instant = (gameMode == PlayFromGameFile) ?
8767         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8768     if(appData.noGUI) return;
8769     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8770         if (!instant) {
8771             if (forwardMostMove == currentMove + 1) {
8772                 AnimateMove(boards[forwardMostMove - 1],
8773                             fromX, fromY, toX, toY);
8774             }
8775             if (appData.highlightLastMove) {
8776                 SetHighlights(fromX, fromY, toX, toY);
8777             }
8778         }
8779         currentMove = forwardMostMove;
8780     }
8781
8782     if (instant) return;
8783
8784     DisplayMove(currentMove - 1);
8785     DrawPosition(FALSE, boards[currentMove]);
8786     DisplayBothClocks();
8787     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8788 }
8789
8790 void SendEgtPath(ChessProgramState *cps)
8791 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8792         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8793
8794         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8795
8796         while(*p) {
8797             char c, *q = name+1, *r, *s;
8798
8799             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8800             while(*p && *p != ',') *q++ = *p++;
8801             *q++ = ':'; *q = 0;
8802             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8803                 strcmp(name, ",nalimov:") == 0 ) {
8804                 // take nalimov path from the menu-changeable option first, if it is defined
8805               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8806                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8807             } else
8808             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8809                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8810                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8811                 s = r = StrStr(s, ":") + 1; // beginning of path info
8812                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8813                 c = *r; *r = 0;             // temporarily null-terminate path info
8814                     *--q = 0;               // strip of trailig ':' from name
8815                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8816                 *r = c;
8817                 SendToProgram(buf,cps);     // send egtbpath command for this format
8818             }
8819             if(*p == ',') p++; // read away comma to position for next format name
8820         }
8821 }
8822
8823 void
8824 InitChessProgram(cps, setup)
8825      ChessProgramState *cps;
8826      int setup; /* [HGM] needed to setup FRC opening position */
8827 {
8828     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8829     if (appData.noChessProgram) return;
8830     hintRequested = FALSE;
8831     bookRequested = FALSE;
8832
8833     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8834     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8835     if(cps->memSize) { /* [HGM] memory */
8836       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8837         SendToProgram(buf, cps);
8838     }
8839     SendEgtPath(cps); /* [HGM] EGT */
8840     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8841       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8842         SendToProgram(buf, cps);
8843     }
8844
8845     SendToProgram(cps->initString, cps);
8846     if (gameInfo.variant != VariantNormal &&
8847         gameInfo.variant != VariantLoadable
8848         /* [HGM] also send variant if board size non-standard */
8849         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8850                                             ) {
8851       char *v = VariantName(gameInfo.variant);
8852       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8853         /* [HGM] in protocol 1 we have to assume all variants valid */
8854         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8855         DisplayFatalError(buf, 0, 1);
8856         return;
8857       }
8858
8859       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8860       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8861       if( gameInfo.variant == VariantXiangqi )
8862            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8863       if( gameInfo.variant == VariantShogi )
8864            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8865       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8866            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8867       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8868                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8869            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8870       if( gameInfo.variant == VariantCourier )
8871            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8872       if( gameInfo.variant == VariantSuper )
8873            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8874       if( gameInfo.variant == VariantGreat )
8875            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8876
8877       if(overruled) {
8878         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8879                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8880            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8881            if(StrStr(cps->variants, b) == NULL) {
8882                // specific sized variant not known, check if general sizing allowed
8883                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8884                    if(StrStr(cps->variants, "boardsize") == NULL) {
8885                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8886                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8887                        DisplayFatalError(buf, 0, 1);
8888                        return;
8889                    }
8890                    /* [HGM] here we really should compare with the maximum supported board size */
8891                }
8892            }
8893       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8894       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8895       SendToProgram(buf, cps);
8896     }
8897     currentlyInitializedVariant = gameInfo.variant;
8898
8899     /* [HGM] send opening position in FRC to first engine */
8900     if(setup) {
8901           SendToProgram("force\n", cps);
8902           SendBoard(cps, 0);
8903           /* engine is now in force mode! Set flag to wake it up after first move. */
8904           setboardSpoiledMachineBlack = 1;
8905     }
8906
8907     if (cps->sendICS) {
8908       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8909       SendToProgram(buf, cps);
8910     }
8911     cps->maybeThinking = FALSE;
8912     cps->offeredDraw = 0;
8913     if (!appData.icsActive) {
8914         SendTimeControl(cps, movesPerSession, timeControl,
8915                         timeIncrement, appData.searchDepth,
8916                         searchTime);
8917     }
8918     if (appData.showThinking
8919         // [HGM] thinking: four options require thinking output to be sent
8920         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8921                                 ) {
8922         SendToProgram("post\n", cps);
8923     }
8924     SendToProgram("hard\n", cps);
8925     if (!appData.ponderNextMove) {
8926         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8927            it without being sure what state we are in first.  "hard"
8928            is not a toggle, so that one is OK.
8929          */
8930         SendToProgram("easy\n", cps);
8931     }
8932     if (cps->usePing) {
8933       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8934       SendToProgram(buf, cps);
8935     }
8936     cps->initDone = TRUE;
8937 }
8938
8939
8940 void
8941 StartChessProgram(cps)
8942      ChessProgramState *cps;
8943 {
8944     char buf[MSG_SIZ];
8945     int err;
8946
8947     if (appData.noChessProgram) return;
8948     cps->initDone = FALSE;
8949
8950     if (strcmp(cps->host, "localhost") == 0) {
8951         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8952     } else if (*appData.remoteShell == NULLCHAR) {
8953         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8954     } else {
8955         if (*appData.remoteUser == NULLCHAR) {
8956           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8957                     cps->program);
8958         } else {
8959           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8960                     cps->host, appData.remoteUser, cps->program);
8961         }
8962         err = StartChildProcess(buf, "", &cps->pr);
8963     }
8964
8965     if (err != 0) {
8966       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8967         DisplayFatalError(buf, err, 1);
8968         cps->pr = NoProc;
8969         cps->isr = NULL;
8970         return;
8971     }
8972
8973     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8974     if (cps->protocolVersion > 1) {
8975       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8976       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8977       cps->comboCnt = 0;  //                and values of combo boxes
8978       SendToProgram(buf, cps);
8979     } else {
8980       SendToProgram("xboard\n", cps);
8981     }
8982 }
8983
8984
8985 void
8986 TwoMachinesEventIfReady P((void))
8987 {
8988   if (first.lastPing != first.lastPong) {
8989     DisplayMessage("", _("Waiting for first chess program"));
8990     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8991     return;
8992   }
8993   if (second.lastPing != second.lastPong) {
8994     DisplayMessage("", _("Waiting for second chess program"));
8995     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8996     return;
8997   }
8998   ThawUI();
8999   TwoMachinesEvent();
9000 }
9001
9002 void
9003 NextMatchGame P((void))
9004 {
9005     int index; /* [HGM] autoinc: step load index during match */
9006     Reset(FALSE, TRUE);
9007     if (*appData.loadGameFile != NULLCHAR) {
9008         index = appData.loadGameIndex;
9009         if(index < 0) { // [HGM] autoinc
9010             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9011             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9012         }
9013         LoadGameFromFile(appData.loadGameFile,
9014                          index,
9015                          appData.loadGameFile, FALSE);
9016     } else if (*appData.loadPositionFile != NULLCHAR) {
9017         index = appData.loadPositionIndex;
9018         if(index < 0) { // [HGM] autoinc
9019             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9020             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9021         }
9022         LoadPositionFromFile(appData.loadPositionFile,
9023                              index,
9024                              appData.loadPositionFile);
9025     }
9026     TwoMachinesEventIfReady();
9027 }
9028
9029 void UserAdjudicationEvent( int result )
9030 {
9031     ChessMove gameResult = GameIsDrawn;
9032
9033     if( result > 0 ) {
9034         gameResult = WhiteWins;
9035     }
9036     else if( result < 0 ) {
9037         gameResult = BlackWins;
9038     }
9039
9040     if( gameMode == TwoMachinesPlay ) {
9041         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9042     }
9043 }
9044
9045
9046 // [HGM] save: calculate checksum of game to make games easily identifiable
9047 int StringCheckSum(char *s)
9048 {
9049         int i = 0;
9050         if(s==NULL) return 0;
9051         while(*s) i = i*259 + *s++;
9052         return i;
9053 }
9054
9055 int GameCheckSum()
9056 {
9057         int i, sum=0;
9058         for(i=backwardMostMove; i<forwardMostMove; i++) {
9059                 sum += pvInfoList[i].depth;
9060                 sum += StringCheckSum(parseList[i]);
9061                 sum += StringCheckSum(commentList[i]);
9062                 sum *= 261;
9063         }
9064         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9065         return sum + StringCheckSum(commentList[i]);
9066 } // end of save patch
9067
9068 void
9069 GameEnds(result, resultDetails, whosays)
9070      ChessMove result;
9071      char *resultDetails;
9072      int whosays;
9073 {
9074     GameMode nextGameMode;
9075     int isIcsGame;
9076     char buf[MSG_SIZ], popupRequested = 0;
9077
9078     if(endingGame) return; /* [HGM] crash: forbid recursion */
9079     endingGame = 1;
9080     if(twoBoards) { // [HGM] dual: switch back to one board
9081         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9082         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9083     }
9084     if (appData.debugMode) {
9085       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9086               result, resultDetails ? resultDetails : "(null)", whosays);
9087     }
9088
9089     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9090
9091     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9092         /* If we are playing on ICS, the server decides when the
9093            game is over, but the engine can offer to draw, claim
9094            a draw, or resign.
9095          */
9096 #if ZIPPY
9097         if (appData.zippyPlay && first.initDone) {
9098             if (result == GameIsDrawn) {
9099                 /* In case draw still needs to be claimed */
9100                 SendToICS(ics_prefix);
9101                 SendToICS("draw\n");
9102             } else if (StrCaseStr(resultDetails, "resign")) {
9103                 SendToICS(ics_prefix);
9104                 SendToICS("resign\n");
9105             }
9106         }
9107 #endif
9108         endingGame = 0; /* [HGM] crash */
9109         return;
9110     }
9111
9112     /* If we're loading the game from a file, stop */
9113     if (whosays == GE_FILE) {
9114       (void) StopLoadGameTimer();
9115       gameFileFP = NULL;
9116     }
9117
9118     /* Cancel draw offers */
9119     first.offeredDraw = second.offeredDraw = 0;
9120
9121     /* If this is an ICS game, only ICS can really say it's done;
9122        if not, anyone can. */
9123     isIcsGame = (gameMode == IcsPlayingWhite ||
9124                  gameMode == IcsPlayingBlack ||
9125                  gameMode == IcsObserving    ||
9126                  gameMode == IcsExamining);
9127
9128     if (!isIcsGame || whosays == GE_ICS) {
9129         /* OK -- not an ICS game, or ICS said it was done */
9130         StopClocks();
9131         if (!isIcsGame && !appData.noChessProgram)
9132           SetUserThinkingEnables();
9133
9134         /* [HGM] if a machine claims the game end we verify this claim */
9135         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9136             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9137                 char claimer;
9138                 ChessMove trueResult = (ChessMove) -1;
9139
9140                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9141                                             first.twoMachinesColor[0] :
9142                                             second.twoMachinesColor[0] ;
9143
9144                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9145                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9146                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9147                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9148                 } else
9149                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9150                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9151                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9152                 } else
9153                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9154                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9155                 }
9156
9157                 // now verify win claims, but not in drop games, as we don't understand those yet
9158                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9159                                                  || gameInfo.variant == VariantGreat) &&
9160                     (result == WhiteWins && claimer == 'w' ||
9161                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9162                       if (appData.debugMode) {
9163                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9164                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9165                       }
9166                       if(result != trueResult) {
9167                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9168                               result = claimer == 'w' ? BlackWins : WhiteWins;
9169                               resultDetails = buf;
9170                       }
9171                 } else
9172                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9173                     && (forwardMostMove <= backwardMostMove ||
9174                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9175                         (claimer=='b')==(forwardMostMove&1))
9176                                                                                   ) {
9177                       /* [HGM] verify: draws that were not flagged are false claims */
9178                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9179                       result = claimer == 'w' ? BlackWins : WhiteWins;
9180                       resultDetails = buf;
9181                 }
9182                 /* (Claiming a loss is accepted no questions asked!) */
9183             }
9184             /* [HGM] bare: don't allow bare King to win */
9185             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9186                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9187                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9188                && result != GameIsDrawn)
9189             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9190                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9191                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9192                         if(p >= 0 && p <= (int)WhiteKing) k++;
9193                 }
9194                 if (appData.debugMode) {
9195                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9196                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9197                 }
9198                 if(k <= 1) {
9199                         result = GameIsDrawn;
9200                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9201                         resultDetails = buf;
9202                 }
9203             }
9204         }
9205
9206
9207         if(serverMoves != NULL && !loadFlag) { char c = '=';
9208             if(result==WhiteWins) c = '+';
9209             if(result==BlackWins) c = '-';
9210             if(resultDetails != NULL)
9211                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9212         }
9213         if (resultDetails != NULL) {
9214             gameInfo.result = result;
9215             gameInfo.resultDetails = StrSave(resultDetails);
9216
9217             /* display last move only if game was not loaded from file */
9218             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9219                 DisplayMove(currentMove - 1);
9220
9221             if (forwardMostMove != 0) {
9222                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9223                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9224                                                                 ) {
9225                     if (*appData.saveGameFile != NULLCHAR) {
9226                         SaveGameToFile(appData.saveGameFile, TRUE);
9227                     } else if (appData.autoSaveGames) {
9228                         AutoSaveGame();
9229                     }
9230                     if (*appData.savePositionFile != NULLCHAR) {
9231                         SavePositionToFile(appData.savePositionFile);
9232                     }
9233                 }
9234             }
9235
9236             /* Tell program how game ended in case it is learning */
9237             /* [HGM] Moved this to after saving the PGN, just in case */
9238             /* engine died and we got here through time loss. In that */
9239             /* case we will get a fatal error writing the pipe, which */
9240             /* would otherwise lose us the PGN.                       */
9241             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9242             /* output during GameEnds should never be fatal anymore   */
9243             if (gameMode == MachinePlaysWhite ||
9244                 gameMode == MachinePlaysBlack ||
9245                 gameMode == TwoMachinesPlay ||
9246                 gameMode == IcsPlayingWhite ||
9247                 gameMode == IcsPlayingBlack ||
9248                 gameMode == BeginningOfGame) {
9249                 char buf[MSG_SIZ];
9250                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9251                         resultDetails);
9252                 if (first.pr != NoProc) {
9253                     SendToProgram(buf, &first);
9254                 }
9255                 if (second.pr != NoProc &&
9256                     gameMode == TwoMachinesPlay) {
9257                     SendToProgram(buf, &second);
9258                 }
9259             }
9260         }
9261
9262         if (appData.icsActive) {
9263             if (appData.quietPlay &&
9264                 (gameMode == IcsPlayingWhite ||
9265                  gameMode == IcsPlayingBlack)) {
9266                 SendToICS(ics_prefix);
9267                 SendToICS("set shout 1\n");
9268             }
9269             nextGameMode = IcsIdle;
9270             ics_user_moved = FALSE;
9271             /* clean up premove.  It's ugly when the game has ended and the
9272              * premove highlights are still on the board.
9273              */
9274             if (gotPremove) {
9275               gotPremove = FALSE;
9276               ClearPremoveHighlights();
9277               DrawPosition(FALSE, boards[currentMove]);
9278             }
9279             if (whosays == GE_ICS) {
9280                 switch (result) {
9281                 case WhiteWins:
9282                     if (gameMode == IcsPlayingWhite)
9283                         PlayIcsWinSound();
9284                     else if(gameMode == IcsPlayingBlack)
9285                         PlayIcsLossSound();
9286                     break;
9287                 case BlackWins:
9288                     if (gameMode == IcsPlayingBlack)
9289                         PlayIcsWinSound();
9290                     else if(gameMode == IcsPlayingWhite)
9291                         PlayIcsLossSound();
9292                     break;
9293                 case GameIsDrawn:
9294                     PlayIcsDrawSound();
9295                     break;
9296                 default:
9297                     PlayIcsUnfinishedSound();
9298                 }
9299             }
9300         } else if (gameMode == EditGame ||
9301                    gameMode == PlayFromGameFile ||
9302                    gameMode == AnalyzeMode ||
9303                    gameMode == AnalyzeFile) {
9304             nextGameMode = gameMode;
9305         } else {
9306             nextGameMode = EndOfGame;
9307         }
9308         pausing = FALSE;
9309         ModeHighlight();
9310     } else {
9311         nextGameMode = gameMode;
9312     }
9313
9314     if (appData.noChessProgram) {
9315         gameMode = nextGameMode;
9316         ModeHighlight();
9317         endingGame = 0; /* [HGM] crash */
9318         return;
9319     }
9320
9321     if (first.reuse) {
9322         /* Put first chess program into idle state */
9323         if (first.pr != NoProc &&
9324             (gameMode == MachinePlaysWhite ||
9325              gameMode == MachinePlaysBlack ||
9326              gameMode == TwoMachinesPlay ||
9327              gameMode == IcsPlayingWhite ||
9328              gameMode == IcsPlayingBlack ||
9329              gameMode == BeginningOfGame)) {
9330             SendToProgram("force\n", &first);
9331             if (first.usePing) {
9332               char buf[MSG_SIZ];
9333               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9334               SendToProgram(buf, &first);
9335             }
9336         }
9337     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9338         /* Kill off first chess program */
9339         if (first.isr != NULL)
9340           RemoveInputSource(first.isr);
9341         first.isr = NULL;
9342
9343         if (first.pr != NoProc) {
9344             ExitAnalyzeMode();
9345             DoSleep( appData.delayBeforeQuit );
9346             SendToProgram("quit\n", &first);
9347             DoSleep( appData.delayAfterQuit );
9348             DestroyChildProcess(first.pr, first.useSigterm);
9349         }
9350         first.pr = NoProc;
9351     }
9352     if (second.reuse) {
9353         /* Put second chess program into idle state */
9354         if (second.pr != NoProc &&
9355             gameMode == TwoMachinesPlay) {
9356             SendToProgram("force\n", &second);
9357             if (second.usePing) {
9358               char buf[MSG_SIZ];
9359               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9360               SendToProgram(buf, &second);
9361             }
9362         }
9363     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9364         /* Kill off second chess program */
9365         if (second.isr != NULL)
9366           RemoveInputSource(second.isr);
9367         second.isr = NULL;
9368
9369         if (second.pr != NoProc) {
9370             DoSleep( appData.delayBeforeQuit );
9371             SendToProgram("quit\n", &second);
9372             DoSleep( appData.delayAfterQuit );
9373             DestroyChildProcess(second.pr, second.useSigterm);
9374         }
9375         second.pr = NoProc;
9376     }
9377
9378     if (matchMode && gameMode == TwoMachinesPlay) {
9379         switch (result) {
9380         case WhiteWins:
9381           if (first.twoMachinesColor[0] == 'w') {
9382             first.matchWins++;
9383           } else {
9384             second.matchWins++;
9385           }
9386           break;
9387         case BlackWins:
9388           if (first.twoMachinesColor[0] == 'b') {
9389             first.matchWins++;
9390           } else {
9391             second.matchWins++;
9392           }
9393           break;
9394         default:
9395           break;
9396         }
9397         if (matchGame < appData.matchGames) {
9398             char *tmp;
9399             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9400                 tmp = first.twoMachinesColor;
9401                 first.twoMachinesColor = second.twoMachinesColor;
9402                 second.twoMachinesColor = tmp;
9403             }
9404             gameMode = nextGameMode;
9405             matchGame++;
9406             if(appData.matchPause>10000 || appData.matchPause<10)
9407                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9408             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9409             endingGame = 0; /* [HGM] crash */
9410             return;
9411         } else {
9412             gameMode = nextGameMode;
9413             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9414                      first.tidy, second.tidy,
9415                      first.matchWins, second.matchWins,
9416                      appData.matchGames - (first.matchWins + second.matchWins));
9417             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9418         }
9419     }
9420     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9421         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9422       ExitAnalyzeMode();
9423     gameMode = nextGameMode;
9424     ModeHighlight();
9425     endingGame = 0;  /* [HGM] crash */
9426     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9427       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9428         matchMode = FALSE; appData.matchGames = matchGame = 0;
9429         DisplayNote(buf);
9430       }
9431     }
9432 }
9433
9434 /* Assumes program was just initialized (initString sent).
9435    Leaves program in force mode. */
9436 void
9437 FeedMovesToProgram(cps, upto)
9438      ChessProgramState *cps;
9439      int upto;
9440 {
9441     int i;
9442
9443     if (appData.debugMode)
9444       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9445               startedFromSetupPosition ? "position and " : "",
9446               backwardMostMove, upto, cps->which);
9447     if(currentlyInitializedVariant != gameInfo.variant) {
9448       char buf[MSG_SIZ];
9449         // [HGM] variantswitch: make engine aware of new variant
9450         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9451                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9452         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9453         SendToProgram(buf, cps);
9454         currentlyInitializedVariant = gameInfo.variant;
9455     }
9456     SendToProgram("force\n", cps);
9457     if (startedFromSetupPosition) {
9458         SendBoard(cps, backwardMostMove);
9459     if (appData.debugMode) {
9460         fprintf(debugFP, "feedMoves\n");
9461     }
9462     }
9463     for (i = backwardMostMove; i < upto; i++) {
9464         SendMoveToProgram(i, cps);
9465     }
9466 }
9467
9468
9469 void
9470 ResurrectChessProgram()
9471 {
9472      /* The chess program may have exited.
9473         If so, restart it and feed it all the moves made so far. */
9474
9475     if (appData.noChessProgram || first.pr != NoProc) return;
9476
9477     StartChessProgram(&first);
9478     InitChessProgram(&first, FALSE);
9479     FeedMovesToProgram(&first, currentMove);
9480
9481     if (!first.sendTime) {
9482         /* can't tell gnuchess what its clock should read,
9483            so we bow to its notion. */
9484         ResetClocks();
9485         timeRemaining[0][currentMove] = whiteTimeRemaining;
9486         timeRemaining[1][currentMove] = blackTimeRemaining;
9487     }
9488
9489     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9490                 appData.icsEngineAnalyze) && first.analysisSupport) {
9491       SendToProgram("analyze\n", &first);
9492       first.analyzing = TRUE;
9493     }
9494 }
9495
9496 /*
9497  * Button procedures
9498  */
9499 void
9500 Reset(redraw, init)
9501      int redraw, init;
9502 {
9503     int i;
9504
9505     if (appData.debugMode) {
9506         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9507                 redraw, init, gameMode);
9508     }
9509     CleanupTail(); // [HGM] vari: delete any stored variations
9510     pausing = pauseExamInvalid = FALSE;
9511     startedFromSetupPosition = blackPlaysFirst = FALSE;
9512     firstMove = TRUE;
9513     whiteFlag = blackFlag = FALSE;
9514     userOfferedDraw = FALSE;
9515     hintRequested = bookRequested = FALSE;
9516     first.maybeThinking = FALSE;
9517     second.maybeThinking = FALSE;
9518     first.bookSuspend = FALSE; // [HGM] book
9519     second.bookSuspend = FALSE;
9520     thinkOutput[0] = NULLCHAR;
9521     lastHint[0] = NULLCHAR;
9522     ClearGameInfo(&gameInfo);
9523     gameInfo.variant = StringToVariant(appData.variant);
9524     ics_user_moved = ics_clock_paused = FALSE;
9525     ics_getting_history = H_FALSE;
9526     ics_gamenum = -1;
9527     white_holding[0] = black_holding[0] = NULLCHAR;
9528     ClearProgramStats();
9529     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9530
9531     ResetFrontEnd();
9532     ClearHighlights();
9533     flipView = appData.flipView;
9534     ClearPremoveHighlights();
9535     gotPremove = FALSE;
9536     alarmSounded = FALSE;
9537
9538     GameEnds(EndOfFile, NULL, GE_PLAYER);
9539     if(appData.serverMovesName != NULL) {
9540         /* [HGM] prepare to make moves file for broadcasting */
9541         clock_t t = clock();
9542         if(serverMoves != NULL) fclose(serverMoves);
9543         serverMoves = fopen(appData.serverMovesName, "r");
9544         if(serverMoves != NULL) {
9545             fclose(serverMoves);
9546             /* delay 15 sec before overwriting, so all clients can see end */
9547             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9548         }
9549         serverMoves = fopen(appData.serverMovesName, "w");
9550     }
9551
9552     ExitAnalyzeMode();
9553     gameMode = BeginningOfGame;
9554     ModeHighlight();
9555     if(appData.icsActive) gameInfo.variant = VariantNormal;
9556     currentMove = forwardMostMove = backwardMostMove = 0;
9557     InitPosition(redraw);
9558     for (i = 0; i < MAX_MOVES; i++) {
9559         if (commentList[i] != NULL) {
9560             free(commentList[i]);
9561             commentList[i] = NULL;
9562         }
9563     }
9564     ResetClocks();
9565     timeRemaining[0][0] = whiteTimeRemaining;
9566     timeRemaining[1][0] = blackTimeRemaining;
9567     if (first.pr == NULL) {
9568         StartChessProgram(&first);
9569     }
9570     if (init) {
9571             InitChessProgram(&first, startedFromSetupPosition);
9572     }
9573     DisplayTitle("");
9574     DisplayMessage("", "");
9575     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9576     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9577 }
9578
9579 void
9580 AutoPlayGameLoop()
9581 {
9582     for (;;) {
9583         if (!AutoPlayOneMove())
9584           return;
9585         if (matchMode || appData.timeDelay == 0)
9586           continue;
9587         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9588           return;
9589         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9590         break;
9591     }
9592 }
9593
9594
9595 int
9596 AutoPlayOneMove()
9597 {
9598     int fromX, fromY, toX, toY;
9599
9600     if (appData.debugMode) {
9601       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9602     }
9603
9604     if (gameMode != PlayFromGameFile)
9605       return FALSE;
9606
9607     if (currentMove >= forwardMostMove) {
9608       gameMode = EditGame;
9609       ModeHighlight();
9610
9611       /* [AS] Clear current move marker at the end of a game */
9612       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9613
9614       return FALSE;
9615     }
9616
9617     toX = moveList[currentMove][2] - AAA;
9618     toY = moveList[currentMove][3] - ONE;
9619
9620     if (moveList[currentMove][1] == '@') {
9621         if (appData.highlightLastMove) {
9622             SetHighlights(-1, -1, toX, toY);
9623         }
9624     } else {
9625         fromX = moveList[currentMove][0] - AAA;
9626         fromY = moveList[currentMove][1] - ONE;
9627
9628         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9629
9630         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9631
9632         if (appData.highlightLastMove) {
9633             SetHighlights(fromX, fromY, toX, toY);
9634         }
9635     }
9636     DisplayMove(currentMove);
9637     SendMoveToProgram(currentMove++, &first);
9638     DisplayBothClocks();
9639     DrawPosition(FALSE, boards[currentMove]);
9640     // [HGM] PV info: always display, routine tests if empty
9641     DisplayComment(currentMove - 1, commentList[currentMove]);
9642     return TRUE;
9643 }
9644
9645
9646 int
9647 LoadGameOneMove(readAhead)
9648      ChessMove readAhead;
9649 {
9650     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9651     char promoChar = NULLCHAR;
9652     ChessMove moveType;
9653     char move[MSG_SIZ];
9654     char *p, *q;
9655
9656     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9657         gameMode != AnalyzeMode && gameMode != Training) {
9658         gameFileFP = NULL;
9659         return FALSE;
9660     }
9661
9662     yyboardindex = forwardMostMove;
9663     if (readAhead != EndOfFile) {
9664       moveType = readAhead;
9665     } else {
9666       if (gameFileFP == NULL)
9667           return FALSE;
9668       moveType = (ChessMove) yylex();
9669     }
9670
9671     done = FALSE;
9672     switch (moveType) {
9673       case Comment:
9674         if (appData.debugMode)
9675           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9676         p = yy_text;
9677
9678         /* append the comment but don't display it */
9679         AppendComment(currentMove, p, FALSE);
9680         return TRUE;
9681
9682       case WhiteCapturesEnPassant:
9683       case BlackCapturesEnPassant:
9684       case WhitePromotion:
9685       case BlackPromotion:
9686       case WhiteNonPromotion:
9687       case BlackNonPromotion:
9688       case NormalMove:
9689       case WhiteKingSideCastle:
9690       case WhiteQueenSideCastle:
9691       case BlackKingSideCastle:
9692       case BlackQueenSideCastle:
9693       case WhiteKingSideCastleWild:
9694       case WhiteQueenSideCastleWild:
9695       case BlackKingSideCastleWild:
9696       case BlackQueenSideCastleWild:
9697       /* PUSH Fabien */
9698       case WhiteHSideCastleFR:
9699       case WhiteASideCastleFR:
9700       case BlackHSideCastleFR:
9701       case BlackASideCastleFR:
9702       /* POP Fabien */
9703         if (appData.debugMode)
9704           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9705         fromX = currentMoveString[0] - AAA;
9706         fromY = currentMoveString[1] - ONE;
9707         toX = currentMoveString[2] - AAA;
9708         toY = currentMoveString[3] - ONE;
9709         promoChar = currentMoveString[4];
9710         break;
9711
9712       case WhiteDrop:
9713       case BlackDrop:
9714         if (appData.debugMode)
9715           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9716         fromX = moveType == WhiteDrop ?
9717           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9718         (int) CharToPiece(ToLower(currentMoveString[0]));
9719         fromY = DROP_RANK;
9720         toX = currentMoveString[2] - AAA;
9721         toY = currentMoveString[3] - ONE;
9722         break;
9723
9724       case WhiteWins:
9725       case BlackWins:
9726       case GameIsDrawn:
9727       case GameUnfinished:
9728         if (appData.debugMode)
9729           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9730         p = strchr(yy_text, '{');
9731         if (p == NULL) p = strchr(yy_text, '(');
9732         if (p == NULL) {
9733             p = yy_text;
9734             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9735         } else {
9736             q = strchr(p, *p == '{' ? '}' : ')');
9737             if (q != NULL) *q = NULLCHAR;
9738             p++;
9739         }
9740         GameEnds(moveType, p, GE_FILE);
9741         done = TRUE;
9742         if (cmailMsgLoaded) {
9743             ClearHighlights();
9744             flipView = WhiteOnMove(currentMove);
9745             if (moveType == GameUnfinished) flipView = !flipView;
9746             if (appData.debugMode)
9747               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9748         }
9749         break;
9750
9751       case EndOfFile:
9752         if (appData.debugMode)
9753           fprintf(debugFP, "Parser hit end of file\n");
9754         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9755           case MT_NONE:
9756           case MT_CHECK:
9757             break;
9758           case MT_CHECKMATE:
9759           case MT_STAINMATE:
9760             if (WhiteOnMove(currentMove)) {
9761                 GameEnds(BlackWins, "Black mates", GE_FILE);
9762             } else {
9763                 GameEnds(WhiteWins, "White mates", GE_FILE);
9764             }
9765             break;
9766           case MT_STALEMATE:
9767             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9768             break;
9769         }
9770         done = TRUE;
9771         break;
9772
9773       case MoveNumberOne:
9774         if (lastLoadGameStart == GNUChessGame) {
9775             /* GNUChessGames have numbers, but they aren't move numbers */
9776             if (appData.debugMode)
9777               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9778                       yy_text, (int) moveType);
9779             return LoadGameOneMove(EndOfFile); /* tail recursion */
9780         }
9781         /* else fall thru */
9782
9783       case XBoardGame:
9784       case GNUChessGame:
9785       case PGNTag:
9786         /* Reached start of next game in file */
9787         if (appData.debugMode)
9788           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9789         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9790           case MT_NONE:
9791           case MT_CHECK:
9792             break;
9793           case MT_CHECKMATE:
9794           case MT_STAINMATE:
9795             if (WhiteOnMove(currentMove)) {
9796                 GameEnds(BlackWins, "Black mates", GE_FILE);
9797             } else {
9798                 GameEnds(WhiteWins, "White mates", GE_FILE);
9799             }
9800             break;
9801           case MT_STALEMATE:
9802             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9803             break;
9804         }
9805         done = TRUE;
9806         break;
9807
9808       case PositionDiagram:     /* should not happen; ignore */
9809       case ElapsedTime:         /* ignore */
9810       case NAG:                 /* ignore */
9811         if (appData.debugMode)
9812           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9813                   yy_text, (int) moveType);
9814         return LoadGameOneMove(EndOfFile); /* tail recursion */
9815
9816       case IllegalMove:
9817         if (appData.testLegality) {
9818             if (appData.debugMode)
9819               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9820             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9821                     (forwardMostMove / 2) + 1,
9822                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9823             DisplayError(move, 0);
9824             done = TRUE;
9825         } else {
9826             if (appData.debugMode)
9827               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9828                       yy_text, currentMoveString);
9829             fromX = currentMoveString[0] - AAA;
9830             fromY = currentMoveString[1] - ONE;
9831             toX = currentMoveString[2] - AAA;
9832             toY = currentMoveString[3] - ONE;
9833             promoChar = currentMoveString[4];
9834         }
9835         break;
9836
9837       case AmbiguousMove:
9838         if (appData.debugMode)
9839           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9840         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9841                 (forwardMostMove / 2) + 1,
9842                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9843         DisplayError(move, 0);
9844         done = TRUE;
9845         break;
9846
9847       default:
9848       case ImpossibleMove:
9849         if (appData.debugMode)
9850           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9851         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9852                 (forwardMostMove / 2) + 1,
9853                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9854         DisplayError(move, 0);
9855         done = TRUE;
9856         break;
9857     }
9858
9859     if (done) {
9860         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9861             DrawPosition(FALSE, boards[currentMove]);
9862             DisplayBothClocks();
9863             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9864               DisplayComment(currentMove - 1, commentList[currentMove]);
9865         }
9866         (void) StopLoadGameTimer();
9867         gameFileFP = NULL;
9868         cmailOldMove = forwardMostMove;
9869         return FALSE;
9870     } else {
9871         /* currentMoveString is set as a side-effect of yylex */
9872         strcat(currentMoveString, "\n");
9873         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9874
9875         thinkOutput[0] = NULLCHAR;
9876         MakeMove(fromX, fromY, toX, toY, promoChar);
9877         currentMove = forwardMostMove;
9878         return TRUE;
9879     }
9880 }
9881
9882 /* Load the nth game from the given file */
9883 int
9884 LoadGameFromFile(filename, n, title, useList)
9885      char *filename;
9886      int n;
9887      char *title;
9888      /*Boolean*/ int useList;
9889 {
9890     FILE *f;
9891     char buf[MSG_SIZ];
9892
9893     if (strcmp(filename, "-") == 0) {
9894         f = stdin;
9895         title = "stdin";
9896     } else {
9897         f = fopen(filename, "rb");
9898         if (f == NULL) {
9899           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9900             DisplayError(buf, errno);
9901             return FALSE;
9902         }
9903     }
9904     if (fseek(f, 0, 0) == -1) {
9905         /* f is not seekable; probably a pipe */
9906         useList = FALSE;
9907     }
9908     if (useList && n == 0) {
9909         int error = GameListBuild(f);
9910         if (error) {
9911             DisplayError(_("Cannot build game list"), error);
9912         } else if (!ListEmpty(&gameList) &&
9913                    ((ListGame *) gameList.tailPred)->number > 1) {
9914             GameListPopUp(f, title);
9915             return TRUE;
9916         }
9917         GameListDestroy();
9918         n = 1;
9919     }
9920     if (n == 0) n = 1;
9921     return LoadGame(f, n, title, FALSE);
9922 }
9923
9924
9925 void
9926 MakeRegisteredMove()
9927 {
9928     int fromX, fromY, toX, toY;
9929     char promoChar;
9930     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9931         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9932           case CMAIL_MOVE:
9933           case CMAIL_DRAW:
9934             if (appData.debugMode)
9935               fprintf(debugFP, "Restoring %s for game %d\n",
9936                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9937
9938             thinkOutput[0] = NULLCHAR;
9939             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9940             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9941             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9942             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9943             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9944             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9945             MakeMove(fromX, fromY, toX, toY, promoChar);
9946             ShowMove(fromX, fromY, toX, toY);
9947
9948             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9949               case MT_NONE:
9950               case MT_CHECK:
9951                 break;
9952
9953               case MT_CHECKMATE:
9954               case MT_STAINMATE:
9955                 if (WhiteOnMove(currentMove)) {
9956                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9957                 } else {
9958                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9959                 }
9960                 break;
9961
9962               case MT_STALEMATE:
9963                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9964                 break;
9965             }
9966
9967             break;
9968
9969           case CMAIL_RESIGN:
9970             if (WhiteOnMove(currentMove)) {
9971                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9972             } else {
9973                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9974             }
9975             break;
9976
9977           case CMAIL_ACCEPT:
9978             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9979             break;
9980
9981           default:
9982             break;
9983         }
9984     }
9985
9986     return;
9987 }
9988
9989 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9990 int
9991 CmailLoadGame(f, gameNumber, title, useList)
9992      FILE *f;
9993      int gameNumber;
9994      char *title;
9995      int useList;
9996 {
9997     int retVal;
9998
9999     if (gameNumber > nCmailGames) {
10000         DisplayError(_("No more games in this message"), 0);
10001         return FALSE;
10002     }
10003     if (f == lastLoadGameFP) {
10004         int offset = gameNumber - lastLoadGameNumber;
10005         if (offset == 0) {
10006             cmailMsg[0] = NULLCHAR;
10007             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10008                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10009                 nCmailMovesRegistered--;
10010             }
10011             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10012             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10013                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10014             }
10015         } else {
10016             if (! RegisterMove()) return FALSE;
10017         }
10018     }
10019
10020     retVal = LoadGame(f, gameNumber, title, useList);
10021
10022     /* Make move registered during previous look at this game, if any */
10023     MakeRegisteredMove();
10024
10025     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10026         commentList[currentMove]
10027           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10028         DisplayComment(currentMove - 1, commentList[currentMove]);
10029     }
10030
10031     return retVal;
10032 }
10033
10034 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10035 int
10036 ReloadGame(offset)
10037      int offset;
10038 {
10039     int gameNumber = lastLoadGameNumber + offset;
10040     if (lastLoadGameFP == NULL) {
10041         DisplayError(_("No game has been loaded yet"), 0);
10042         return FALSE;
10043     }
10044     if (gameNumber <= 0) {
10045         DisplayError(_("Can't back up any further"), 0);
10046         return FALSE;
10047     }
10048     if (cmailMsgLoaded) {
10049         return CmailLoadGame(lastLoadGameFP, gameNumber,
10050                              lastLoadGameTitle, lastLoadGameUseList);
10051     } else {
10052         return LoadGame(lastLoadGameFP, gameNumber,
10053                         lastLoadGameTitle, lastLoadGameUseList);
10054     }
10055 }
10056
10057
10058
10059 /* Load the nth game from open file f */
10060 int
10061 LoadGame(f, gameNumber, title, useList)
10062      FILE *f;
10063      int gameNumber;
10064      char *title;
10065      int useList;
10066 {
10067     ChessMove cm;
10068     char buf[MSG_SIZ];
10069     int gn = gameNumber;
10070     ListGame *lg = NULL;
10071     int numPGNTags = 0;
10072     int err;
10073     GameMode oldGameMode;
10074     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10075
10076     if (appData.debugMode)
10077         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10078
10079     if (gameMode == Training )
10080         SetTrainingModeOff();
10081
10082     oldGameMode = gameMode;
10083     if (gameMode != BeginningOfGame) {
10084       Reset(FALSE, TRUE);
10085     }
10086
10087     gameFileFP = f;
10088     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10089         fclose(lastLoadGameFP);
10090     }
10091
10092     if (useList) {
10093         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10094
10095         if (lg) {
10096             fseek(f, lg->offset, 0);
10097             GameListHighlight(gameNumber);
10098             gn = 1;
10099         }
10100         else {
10101             DisplayError(_("Game number out of range"), 0);
10102             return FALSE;
10103         }
10104     } else {
10105         GameListDestroy();
10106         if (fseek(f, 0, 0) == -1) {
10107             if (f == lastLoadGameFP ?
10108                 gameNumber == lastLoadGameNumber + 1 :
10109                 gameNumber == 1) {
10110                 gn = 1;
10111             } else {
10112                 DisplayError(_("Can't seek on game file"), 0);
10113                 return FALSE;
10114             }
10115         }
10116     }
10117     lastLoadGameFP = f;
10118     lastLoadGameNumber = gameNumber;
10119     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10120     lastLoadGameUseList = useList;
10121
10122     yynewfile(f);
10123
10124     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10125       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10126                 lg->gameInfo.black);
10127             DisplayTitle(buf);
10128     } else if (*title != NULLCHAR) {
10129         if (gameNumber > 1) {
10130           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10131             DisplayTitle(buf);
10132         } else {
10133             DisplayTitle(title);
10134         }
10135     }
10136
10137     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10138         gameMode = PlayFromGameFile;
10139         ModeHighlight();
10140     }
10141
10142     currentMove = forwardMostMove = backwardMostMove = 0;
10143     CopyBoard(boards[0], initialPosition);
10144     StopClocks();
10145
10146     /*
10147      * Skip the first gn-1 games in the file.
10148      * Also skip over anything that precedes an identifiable
10149      * start of game marker, to avoid being confused by
10150      * garbage at the start of the file.  Currently
10151      * recognized start of game markers are the move number "1",
10152      * the pattern "gnuchess .* game", the pattern
10153      * "^[#;%] [^ ]* game file", and a PGN tag block.
10154      * A game that starts with one of the latter two patterns
10155      * will also have a move number 1, possibly
10156      * following a position diagram.
10157      * 5-4-02: Let's try being more lenient and allowing a game to
10158      * start with an unnumbered move.  Does that break anything?
10159      */
10160     cm = lastLoadGameStart = EndOfFile;
10161     while (gn > 0) {
10162         yyboardindex = forwardMostMove;
10163         cm = (ChessMove) yylex();
10164         switch (cm) {
10165           case EndOfFile:
10166             if (cmailMsgLoaded) {
10167                 nCmailGames = CMAIL_MAX_GAMES - gn;
10168             } else {
10169                 Reset(TRUE, TRUE);
10170                 DisplayError(_("Game not found in file"), 0);
10171             }
10172             return FALSE;
10173
10174           case GNUChessGame:
10175           case XBoardGame:
10176             gn--;
10177             lastLoadGameStart = cm;
10178             break;
10179
10180           case MoveNumberOne:
10181             switch (lastLoadGameStart) {
10182               case GNUChessGame:
10183               case XBoardGame:
10184               case PGNTag:
10185                 break;
10186               case MoveNumberOne:
10187               case EndOfFile:
10188                 gn--;           /* count this game */
10189                 lastLoadGameStart = cm;
10190                 break;
10191               default:
10192                 /* impossible */
10193                 break;
10194             }
10195             break;
10196
10197           case PGNTag:
10198             switch (lastLoadGameStart) {
10199               case GNUChessGame:
10200               case PGNTag:
10201               case MoveNumberOne:
10202               case EndOfFile:
10203                 gn--;           /* count this game */
10204                 lastLoadGameStart = cm;
10205                 break;
10206               case XBoardGame:
10207                 lastLoadGameStart = cm; /* game counted already */
10208                 break;
10209               default:
10210                 /* impossible */
10211                 break;
10212             }
10213             if (gn > 0) {
10214                 do {
10215                     yyboardindex = forwardMostMove;
10216                     cm = (ChessMove) yylex();
10217                 } while (cm == PGNTag || cm == Comment);
10218             }
10219             break;
10220
10221           case WhiteWins:
10222           case BlackWins:
10223           case GameIsDrawn:
10224             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10225                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10226                     != CMAIL_OLD_RESULT) {
10227                     nCmailResults ++ ;
10228                     cmailResult[  CMAIL_MAX_GAMES
10229                                 - gn - 1] = CMAIL_OLD_RESULT;
10230                 }
10231             }
10232             break;
10233
10234           case NormalMove:
10235             /* Only a NormalMove can be at the start of a game
10236              * without a position diagram. */
10237             if (lastLoadGameStart == EndOfFile ) {
10238               gn--;
10239               lastLoadGameStart = MoveNumberOne;
10240             }
10241             break;
10242
10243           default:
10244             break;
10245         }
10246     }
10247
10248     if (appData.debugMode)
10249       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10250
10251     if (cm == XBoardGame) {
10252         /* Skip any header junk before position diagram and/or move 1 */
10253         for (;;) {
10254             yyboardindex = forwardMostMove;
10255             cm = (ChessMove) yylex();
10256
10257             if (cm == EndOfFile ||
10258                 cm == GNUChessGame || cm == XBoardGame) {
10259                 /* Empty game; pretend end-of-file and handle later */
10260                 cm = EndOfFile;
10261                 break;
10262             }
10263
10264             if (cm == MoveNumberOne || cm == PositionDiagram ||
10265                 cm == PGNTag || cm == Comment)
10266               break;
10267         }
10268     } else if (cm == GNUChessGame) {
10269         if (gameInfo.event != NULL) {
10270             free(gameInfo.event);
10271         }
10272         gameInfo.event = StrSave(yy_text);
10273     }
10274
10275     startedFromSetupPosition = FALSE;
10276     while (cm == PGNTag) {
10277         if (appData.debugMode)
10278           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10279         err = ParsePGNTag(yy_text, &gameInfo);
10280         if (!err) numPGNTags++;
10281
10282         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10283         if(gameInfo.variant != oldVariant) {
10284             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10285             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10286             InitPosition(TRUE);
10287             oldVariant = gameInfo.variant;
10288             if (appData.debugMode)
10289               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10290         }
10291
10292
10293         if (gameInfo.fen != NULL) {
10294           Board initial_position;
10295           startedFromSetupPosition = TRUE;
10296           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10297             Reset(TRUE, TRUE);
10298             DisplayError(_("Bad FEN position in file"), 0);
10299             return FALSE;
10300           }
10301           CopyBoard(boards[0], initial_position);
10302           if (blackPlaysFirst) {
10303             currentMove = forwardMostMove = backwardMostMove = 1;
10304             CopyBoard(boards[1], initial_position);
10305             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10306             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10307             timeRemaining[0][1] = whiteTimeRemaining;
10308             timeRemaining[1][1] = blackTimeRemaining;
10309             if (commentList[0] != NULL) {
10310               commentList[1] = commentList[0];
10311               commentList[0] = NULL;
10312             }
10313           } else {
10314             currentMove = forwardMostMove = backwardMostMove = 0;
10315           }
10316           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10317           {   int i;
10318               initialRulePlies = FENrulePlies;
10319               for( i=0; i< nrCastlingRights; i++ )
10320                   initialRights[i] = initial_position[CASTLING][i];
10321           }
10322           yyboardindex = forwardMostMove;
10323           free(gameInfo.fen);
10324           gameInfo.fen = NULL;
10325         }
10326
10327         yyboardindex = forwardMostMove;
10328         cm = (ChessMove) yylex();
10329
10330         /* Handle comments interspersed among the tags */
10331         while (cm == Comment) {
10332             char *p;
10333             if (appData.debugMode)
10334               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10335             p = yy_text;
10336             AppendComment(currentMove, p, FALSE);
10337             yyboardindex = forwardMostMove;
10338             cm = (ChessMove) yylex();
10339         }
10340     }
10341
10342     /* don't rely on existence of Event tag since if game was
10343      * pasted from clipboard the Event tag may not exist
10344      */
10345     if (numPGNTags > 0){
10346         char *tags;
10347         if (gameInfo.variant == VariantNormal) {
10348           VariantClass v = StringToVariant(gameInfo.event);
10349           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10350           if(v < VariantShogi) gameInfo.variant = v;
10351         }
10352         if (!matchMode) {
10353           if( appData.autoDisplayTags ) {
10354             tags = PGNTags(&gameInfo);
10355             TagsPopUp(tags, CmailMsg());
10356             free(tags);
10357           }
10358         }
10359     } else {
10360         /* Make something up, but don't display it now */
10361         SetGameInfo();
10362         TagsPopDown();
10363     }
10364
10365     if (cm == PositionDiagram) {
10366         int i, j;
10367         char *p;
10368         Board initial_position;
10369
10370         if (appData.debugMode)
10371           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10372
10373         if (!startedFromSetupPosition) {
10374             p = yy_text;
10375             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10376               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10377                 switch (*p) {
10378                   case '[':
10379                   case '-':
10380                   case ' ':
10381                   case '\t':
10382                   case '\n':
10383                   case '\r':
10384                     break;
10385                   default:
10386                     initial_position[i][j++] = CharToPiece(*p);
10387                     break;
10388                 }
10389             while (*p == ' ' || *p == '\t' ||
10390                    *p == '\n' || *p == '\r') p++;
10391
10392             if (strncmp(p, "black", strlen("black"))==0)
10393               blackPlaysFirst = TRUE;
10394             else
10395               blackPlaysFirst = FALSE;
10396             startedFromSetupPosition = TRUE;
10397
10398             CopyBoard(boards[0], initial_position);
10399             if (blackPlaysFirst) {
10400                 currentMove = forwardMostMove = backwardMostMove = 1;
10401                 CopyBoard(boards[1], initial_position);
10402                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10403                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10404                 timeRemaining[0][1] = whiteTimeRemaining;
10405                 timeRemaining[1][1] = blackTimeRemaining;
10406                 if (commentList[0] != NULL) {
10407                     commentList[1] = commentList[0];
10408                     commentList[0] = NULL;
10409                 }
10410             } else {
10411                 currentMove = forwardMostMove = backwardMostMove = 0;
10412             }
10413         }
10414         yyboardindex = forwardMostMove;
10415         cm = (ChessMove) yylex();
10416     }
10417
10418     if (first.pr == NoProc) {
10419         StartChessProgram(&first);
10420     }
10421     InitChessProgram(&first, FALSE);
10422     SendToProgram("force\n", &first);
10423     if (startedFromSetupPosition) {
10424         SendBoard(&first, forwardMostMove);
10425     if (appData.debugMode) {
10426         fprintf(debugFP, "Load Game\n");
10427     }
10428         DisplayBothClocks();
10429     }
10430
10431     /* [HGM] server: flag to write setup moves in broadcast file as one */
10432     loadFlag = appData.suppressLoadMoves;
10433
10434     while (cm == Comment) {
10435         char *p;
10436         if (appData.debugMode)
10437           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10438         p = yy_text;
10439         AppendComment(currentMove, p, FALSE);
10440         yyboardindex = forwardMostMove;
10441         cm = (ChessMove) yylex();
10442     }
10443
10444     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10445         cm == WhiteWins || cm == BlackWins ||
10446         cm == GameIsDrawn || cm == GameUnfinished) {
10447         DisplayMessage("", _("No moves in game"));
10448         if (cmailMsgLoaded) {
10449             if (appData.debugMode)
10450               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10451             ClearHighlights();
10452             flipView = FALSE;
10453         }
10454         DrawPosition(FALSE, boards[currentMove]);
10455         DisplayBothClocks();
10456         gameMode = EditGame;
10457         ModeHighlight();
10458         gameFileFP = NULL;
10459         cmailOldMove = 0;
10460         return TRUE;
10461     }
10462
10463     // [HGM] PV info: routine tests if comment empty
10464     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10465         DisplayComment(currentMove - 1, commentList[currentMove]);
10466     }
10467     if (!matchMode && appData.timeDelay != 0)
10468       DrawPosition(FALSE, boards[currentMove]);
10469
10470     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10471       programStats.ok_to_send = 1;
10472     }
10473
10474     /* if the first token after the PGN tags is a move
10475      * and not move number 1, retrieve it from the parser
10476      */
10477     if (cm != MoveNumberOne)
10478         LoadGameOneMove(cm);
10479
10480     /* load the remaining moves from the file */
10481     while (LoadGameOneMove(EndOfFile)) {
10482       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10483       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10484     }
10485
10486     /* rewind to the start of the game */
10487     currentMove = backwardMostMove;
10488
10489     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10490
10491     if (oldGameMode == AnalyzeFile ||
10492         oldGameMode == AnalyzeMode) {
10493       AnalyzeFileEvent();
10494     }
10495
10496     if (matchMode || appData.timeDelay == 0) {
10497       ToEndEvent();
10498       gameMode = EditGame;
10499       ModeHighlight();
10500     } else if (appData.timeDelay > 0) {
10501       AutoPlayGameLoop();
10502     }
10503
10504     if (appData.debugMode)
10505         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10506
10507     loadFlag = 0; /* [HGM] true game starts */
10508     return TRUE;
10509 }
10510
10511 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10512 int
10513 ReloadPosition(offset)
10514      int offset;
10515 {
10516     int positionNumber = lastLoadPositionNumber + offset;
10517     if (lastLoadPositionFP == NULL) {
10518         DisplayError(_("No position has been loaded yet"), 0);
10519         return FALSE;
10520     }
10521     if (positionNumber <= 0) {
10522         DisplayError(_("Can't back up any further"), 0);
10523         return FALSE;
10524     }
10525     return LoadPosition(lastLoadPositionFP, positionNumber,
10526                         lastLoadPositionTitle);
10527 }
10528
10529 /* Load the nth position from the given file */
10530 int
10531 LoadPositionFromFile(filename, n, title)
10532      char *filename;
10533      int n;
10534      char *title;
10535 {
10536     FILE *f;
10537     char buf[MSG_SIZ];
10538
10539     if (strcmp(filename, "-") == 0) {
10540         return LoadPosition(stdin, n, "stdin");
10541     } else {
10542         f = fopen(filename, "rb");
10543         if (f == NULL) {
10544             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10545             DisplayError(buf, errno);
10546             return FALSE;
10547         } else {
10548             return LoadPosition(f, n, title);
10549         }
10550     }
10551 }
10552
10553 /* Load the nth position from the given open file, and close it */
10554 int
10555 LoadPosition(f, positionNumber, title)
10556      FILE *f;
10557      int positionNumber;
10558      char *title;
10559 {
10560     char *p, line[MSG_SIZ];
10561     Board initial_position;
10562     int i, j, fenMode, pn;
10563
10564     if (gameMode == Training )
10565         SetTrainingModeOff();
10566
10567     if (gameMode != BeginningOfGame) {
10568         Reset(FALSE, TRUE);
10569     }
10570     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10571         fclose(lastLoadPositionFP);
10572     }
10573     if (positionNumber == 0) positionNumber = 1;
10574     lastLoadPositionFP = f;
10575     lastLoadPositionNumber = positionNumber;
10576     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10577     if (first.pr == NoProc) {
10578       StartChessProgram(&first);
10579       InitChessProgram(&first, FALSE);
10580     }
10581     pn = positionNumber;
10582     if (positionNumber < 0) {
10583         /* Negative position number means to seek to that byte offset */
10584         if (fseek(f, -positionNumber, 0) == -1) {
10585             DisplayError(_("Can't seek on position file"), 0);
10586             return FALSE;
10587         };
10588         pn = 1;
10589     } else {
10590         if (fseek(f, 0, 0) == -1) {
10591             if (f == lastLoadPositionFP ?
10592                 positionNumber == lastLoadPositionNumber + 1 :
10593                 positionNumber == 1) {
10594                 pn = 1;
10595             } else {
10596                 DisplayError(_("Can't seek on position file"), 0);
10597                 return FALSE;
10598             }
10599         }
10600     }
10601     /* See if this file is FEN or old-style xboard */
10602     if (fgets(line, MSG_SIZ, f) == NULL) {
10603         DisplayError(_("Position not found in file"), 0);
10604         return FALSE;
10605     }
10606     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10607     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10608
10609     if (pn >= 2) {
10610         if (fenMode || line[0] == '#') pn--;
10611         while (pn > 0) {
10612             /* skip positions before number pn */
10613             if (fgets(line, MSG_SIZ, f) == NULL) {
10614                 Reset(TRUE, TRUE);
10615                 DisplayError(_("Position not found in file"), 0);
10616                 return FALSE;
10617             }
10618             if (fenMode || line[0] == '#') pn--;
10619         }
10620     }
10621
10622     if (fenMode) {
10623         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10624             DisplayError(_("Bad FEN position in file"), 0);
10625             return FALSE;
10626         }
10627     } else {
10628         (void) fgets(line, MSG_SIZ, f);
10629         (void) fgets(line, MSG_SIZ, f);
10630
10631         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10632             (void) fgets(line, MSG_SIZ, f);
10633             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10634                 if (*p == ' ')
10635                   continue;
10636                 initial_position[i][j++] = CharToPiece(*p);
10637             }
10638         }
10639
10640         blackPlaysFirst = FALSE;
10641         if (!feof(f)) {
10642             (void) fgets(line, MSG_SIZ, f);
10643             if (strncmp(line, "black", strlen("black"))==0)
10644               blackPlaysFirst = TRUE;
10645         }
10646     }
10647     startedFromSetupPosition = TRUE;
10648
10649     SendToProgram("force\n", &first);
10650     CopyBoard(boards[0], initial_position);
10651     if (blackPlaysFirst) {
10652         currentMove = forwardMostMove = backwardMostMove = 1;
10653         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10654         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10655         CopyBoard(boards[1], initial_position);
10656         DisplayMessage("", _("Black to play"));
10657     } else {
10658         currentMove = forwardMostMove = backwardMostMove = 0;
10659         DisplayMessage("", _("White to play"));
10660     }
10661     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10662     SendBoard(&first, forwardMostMove);
10663     if (appData.debugMode) {
10664 int i, j;
10665   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10666   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10667         fprintf(debugFP, "Load Position\n");
10668     }
10669
10670     if (positionNumber > 1) {
10671       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10672         DisplayTitle(line);
10673     } else {
10674         DisplayTitle(title);
10675     }
10676     gameMode = EditGame;
10677     ModeHighlight();
10678     ResetClocks();
10679     timeRemaining[0][1] = whiteTimeRemaining;
10680     timeRemaining[1][1] = blackTimeRemaining;
10681     DrawPosition(FALSE, boards[currentMove]);
10682
10683     return TRUE;
10684 }
10685
10686
10687 void
10688 CopyPlayerNameIntoFileName(dest, src)
10689      char **dest, *src;
10690 {
10691     while (*src != NULLCHAR && *src != ',') {
10692         if (*src == ' ') {
10693             *(*dest)++ = '_';
10694             src++;
10695         } else {
10696             *(*dest)++ = *src++;
10697         }
10698     }
10699 }
10700
10701 char *DefaultFileName(ext)
10702      char *ext;
10703 {
10704     static char def[MSG_SIZ];
10705     char *p;
10706
10707     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10708         p = def;
10709         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10710         *p++ = '-';
10711         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10712         *p++ = '.';
10713         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10714     } else {
10715         def[0] = NULLCHAR;
10716     }
10717     return def;
10718 }
10719
10720 /* Save the current game to the given file */
10721 int
10722 SaveGameToFile(filename, append)
10723      char *filename;
10724      int append;
10725 {
10726     FILE *f;
10727     char buf[MSG_SIZ];
10728
10729     if (strcmp(filename, "-") == 0) {
10730         return SaveGame(stdout, 0, NULL);
10731     } else {
10732         f = fopen(filename, append ? "a" : "w");
10733         if (f == NULL) {
10734             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10735             DisplayError(buf, errno);
10736             return FALSE;
10737         } else {
10738             return SaveGame(f, 0, NULL);
10739         }
10740     }
10741 }
10742
10743 char *
10744 SavePart(str)
10745      char *str;
10746 {
10747     static char buf[MSG_SIZ];
10748     char *p;
10749
10750     p = strchr(str, ' ');
10751     if (p == NULL) return str;
10752     strncpy(buf, str, p - str);
10753     buf[p - str] = NULLCHAR;
10754     return buf;
10755 }
10756
10757 #define PGN_MAX_LINE 75
10758
10759 #define PGN_SIDE_WHITE  0
10760 #define PGN_SIDE_BLACK  1
10761
10762 /* [AS] */
10763 static int FindFirstMoveOutOfBook( int side )
10764 {
10765     int result = -1;
10766
10767     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10768         int index = backwardMostMove;
10769         int has_book_hit = 0;
10770
10771         if( (index % 2) != side ) {
10772             index++;
10773         }
10774
10775         while( index < forwardMostMove ) {
10776             /* Check to see if engine is in book */
10777             int depth = pvInfoList[index].depth;
10778             int score = pvInfoList[index].score;
10779             int in_book = 0;
10780
10781             if( depth <= 2 ) {
10782                 in_book = 1;
10783             }
10784             else if( score == 0 && depth == 63 ) {
10785                 in_book = 1; /* Zappa */
10786             }
10787             else if( score == 2 && depth == 99 ) {
10788                 in_book = 1; /* Abrok */
10789             }
10790
10791             has_book_hit += in_book;
10792
10793             if( ! in_book ) {
10794                 result = index;
10795
10796                 break;
10797             }
10798
10799             index += 2;
10800         }
10801     }
10802
10803     return result;
10804 }
10805
10806 /* [AS] */
10807 void GetOutOfBookInfo( char * buf )
10808 {
10809     int oob[2];
10810     int i;
10811     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10812
10813     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10814     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10815
10816     *buf = '\0';
10817
10818     if( oob[0] >= 0 || oob[1] >= 0 ) {
10819         for( i=0; i<2; i++ ) {
10820             int idx = oob[i];
10821
10822             if( idx >= 0 ) {
10823                 if( i > 0 && oob[0] >= 0 ) {
10824                     strcat( buf, "   " );
10825                 }
10826
10827                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10828                 sprintf( buf+strlen(buf), "%s%.2f",
10829                     pvInfoList[idx].score >= 0 ? "+" : "",
10830                     pvInfoList[idx].score / 100.0 );
10831             }
10832         }
10833     }
10834 }
10835
10836 /* Save game in PGN style and close the file */
10837 int
10838 SaveGamePGN(f)
10839      FILE *f;
10840 {
10841     int i, offset, linelen, newblock;
10842     time_t tm;
10843 //    char *movetext;
10844     char numtext[32];
10845     int movelen, numlen, blank;
10846     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10847
10848     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10849
10850     tm = time((time_t *) NULL);
10851
10852     PrintPGNTags(f, &gameInfo);
10853
10854     if (backwardMostMove > 0 || startedFromSetupPosition) {
10855         char *fen = PositionToFEN(backwardMostMove, NULL);
10856         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10857         fprintf(f, "\n{--------------\n");
10858         PrintPosition(f, backwardMostMove);
10859         fprintf(f, "--------------}\n");
10860         free(fen);
10861     }
10862     else {
10863         /* [AS] Out of book annotation */
10864         if( appData.saveOutOfBookInfo ) {
10865             char buf[64];
10866
10867             GetOutOfBookInfo( buf );
10868
10869             if( buf[0] != '\0' ) {
10870                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10871             }
10872         }
10873
10874         fprintf(f, "\n");
10875     }
10876
10877     i = backwardMostMove;
10878     linelen = 0;
10879     newblock = TRUE;
10880
10881     while (i < forwardMostMove) {
10882         /* Print comments preceding this move */
10883         if (commentList[i] != NULL) {
10884             if (linelen > 0) fprintf(f, "\n");
10885             fprintf(f, "%s", commentList[i]);
10886             linelen = 0;
10887             newblock = TRUE;
10888         }
10889
10890         /* Format move number */
10891         if ((i % 2) == 0)
10892           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10893         else
10894           if (newblock)
10895             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10896           else
10897             numtext[0] = NULLCHAR;
10898
10899         numlen = strlen(numtext);
10900         newblock = FALSE;
10901
10902         /* Print move number */
10903         blank = linelen > 0 && numlen > 0;
10904         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10905             fprintf(f, "\n");
10906             linelen = 0;
10907             blank = 0;
10908         }
10909         if (blank) {
10910             fprintf(f, " ");
10911             linelen++;
10912         }
10913         fprintf(f, "%s", numtext);
10914         linelen += numlen;
10915
10916         /* Get move */
10917         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10918         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10919
10920         /* Print move */
10921         blank = linelen > 0 && movelen > 0;
10922         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10923             fprintf(f, "\n");
10924             linelen = 0;
10925             blank = 0;
10926         }
10927         if (blank) {
10928             fprintf(f, " ");
10929             linelen++;
10930         }
10931         fprintf(f, "%s", move_buffer);
10932         linelen += movelen;
10933
10934         /* [AS] Add PV info if present */
10935         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10936             /* [HGM] add time */
10937             char buf[MSG_SIZ]; int seconds;
10938
10939             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10940
10941             if( seconds <= 0)
10942               buf[0] = 0;
10943             else
10944               if( seconds < 30 )
10945                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10946               else
10947                 {
10948                   seconds = (seconds + 4)/10; // round to full seconds
10949                   if( seconds < 60 )
10950                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10951                   else
10952                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10953                 }
10954
10955             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10956                       pvInfoList[i].score >= 0 ? "+" : "",
10957                       pvInfoList[i].score / 100.0,
10958                       pvInfoList[i].depth,
10959                       buf );
10960
10961             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10962
10963             /* Print score/depth */
10964             blank = linelen > 0 && movelen > 0;
10965             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10966                 fprintf(f, "\n");
10967                 linelen = 0;
10968                 blank = 0;
10969             }
10970             if (blank) {
10971                 fprintf(f, " ");
10972                 linelen++;
10973             }
10974             fprintf(f, "%s", move_buffer);
10975             linelen += movelen;
10976         }
10977
10978         i++;
10979     }
10980
10981     /* Start a new line */
10982     if (linelen > 0) fprintf(f, "\n");
10983
10984     /* Print comments after last move */
10985     if (commentList[i] != NULL) {
10986         fprintf(f, "%s\n", commentList[i]);
10987     }
10988
10989     /* Print result */
10990     if (gameInfo.resultDetails != NULL &&
10991         gameInfo.resultDetails[0] != NULLCHAR) {
10992         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10993                 PGNResult(gameInfo.result));
10994     } else {
10995         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10996     }
10997
10998     fclose(f);
10999     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11000     return TRUE;
11001 }
11002
11003 /* Save game in old style and close the file */
11004 int
11005 SaveGameOldStyle(f)
11006      FILE *f;
11007 {
11008     int i, offset;
11009     time_t tm;
11010
11011     tm = time((time_t *) NULL);
11012
11013     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11014     PrintOpponents(f);
11015
11016     if (backwardMostMove > 0 || startedFromSetupPosition) {
11017         fprintf(f, "\n[--------------\n");
11018         PrintPosition(f, backwardMostMove);
11019         fprintf(f, "--------------]\n");
11020     } else {
11021         fprintf(f, "\n");
11022     }
11023
11024     i = backwardMostMove;
11025     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11026
11027     while (i < forwardMostMove) {
11028         if (commentList[i] != NULL) {
11029             fprintf(f, "[%s]\n", commentList[i]);
11030         }
11031
11032         if ((i % 2) == 1) {
11033             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11034             i++;
11035         } else {
11036             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11037             i++;
11038             if (commentList[i] != NULL) {
11039                 fprintf(f, "\n");
11040                 continue;
11041             }
11042             if (i >= forwardMostMove) {
11043                 fprintf(f, "\n");
11044                 break;
11045             }
11046             fprintf(f, "%s\n", parseList[i]);
11047             i++;
11048         }
11049     }
11050
11051     if (commentList[i] != NULL) {
11052         fprintf(f, "[%s]\n", commentList[i]);
11053     }
11054
11055     /* This isn't really the old style, but it's close enough */
11056     if (gameInfo.resultDetails != NULL &&
11057         gameInfo.resultDetails[0] != NULLCHAR) {
11058         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11059                 gameInfo.resultDetails);
11060     } else {
11061         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11062     }
11063
11064     fclose(f);
11065     return TRUE;
11066 }
11067
11068 /* Save the current game to open file f and close the file */
11069 int
11070 SaveGame(f, dummy, dummy2)
11071      FILE *f;
11072      int dummy;
11073      char *dummy2;
11074 {
11075     if (gameMode == EditPosition) EditPositionDone(TRUE);
11076     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11077     if (appData.oldSaveStyle)
11078       return SaveGameOldStyle(f);
11079     else
11080       return SaveGamePGN(f);
11081 }
11082
11083 /* Save the current position to the given file */
11084 int
11085 SavePositionToFile(filename)
11086      char *filename;
11087 {
11088     FILE *f;
11089     char buf[MSG_SIZ];
11090
11091     if (strcmp(filename, "-") == 0) {
11092         return SavePosition(stdout, 0, NULL);
11093     } else {
11094         f = fopen(filename, "a");
11095         if (f == NULL) {
11096             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11097             DisplayError(buf, errno);
11098             return FALSE;
11099         } else {
11100             SavePosition(f, 0, NULL);
11101             return TRUE;
11102         }
11103     }
11104 }
11105
11106 /* Save the current position to the given open file and close the file */
11107 int
11108 SavePosition(f, dummy, dummy2)
11109      FILE *f;
11110      int dummy;
11111      char *dummy2;
11112 {
11113     time_t tm;
11114     char *fen;
11115
11116     if (gameMode == EditPosition) EditPositionDone(TRUE);
11117     if (appData.oldSaveStyle) {
11118         tm = time((time_t *) NULL);
11119
11120         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11121         PrintOpponents(f);
11122         fprintf(f, "[--------------\n");
11123         PrintPosition(f, currentMove);
11124         fprintf(f, "--------------]\n");
11125     } else {
11126         fen = PositionToFEN(currentMove, NULL);
11127         fprintf(f, "%s\n", fen);
11128         free(fen);
11129     }
11130     fclose(f);
11131     return TRUE;
11132 }
11133
11134 void
11135 ReloadCmailMsgEvent(unregister)
11136      int unregister;
11137 {
11138 #if !WIN32
11139     static char *inFilename = NULL;
11140     static char *outFilename;
11141     int i;
11142     struct stat inbuf, outbuf;
11143     int status;
11144
11145     /* Any registered moves are unregistered if unregister is set, */
11146     /* i.e. invoked by the signal handler */
11147     if (unregister) {
11148         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11149             cmailMoveRegistered[i] = FALSE;
11150             if (cmailCommentList[i] != NULL) {
11151                 free(cmailCommentList[i]);
11152                 cmailCommentList[i] = NULL;
11153             }
11154         }
11155         nCmailMovesRegistered = 0;
11156     }
11157
11158     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11159         cmailResult[i] = CMAIL_NOT_RESULT;
11160     }
11161     nCmailResults = 0;
11162
11163     if (inFilename == NULL) {
11164         /* Because the filenames are static they only get malloced once  */
11165         /* and they never get freed                                      */
11166         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11167         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11168
11169         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11170         sprintf(outFilename, "%s.out", appData.cmailGameName);
11171     }
11172
11173     status = stat(outFilename, &outbuf);
11174     if (status < 0) {
11175         cmailMailedMove = FALSE;
11176     } else {
11177         status = stat(inFilename, &inbuf);
11178         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11179     }
11180
11181     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11182        counts the games, notes how each one terminated, etc.
11183
11184        It would be nice to remove this kludge and instead gather all
11185        the information while building the game list.  (And to keep it
11186        in the game list nodes instead of having a bunch of fixed-size
11187        parallel arrays.)  Note this will require getting each game's
11188        termination from the PGN tags, as the game list builder does
11189        not process the game moves.  --mann
11190        */
11191     cmailMsgLoaded = TRUE;
11192     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11193
11194     /* Load first game in the file or popup game menu */
11195     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11196
11197 #endif /* !WIN32 */
11198     return;
11199 }
11200
11201 int
11202 RegisterMove()
11203 {
11204     FILE *f;
11205     char string[MSG_SIZ];
11206
11207     if (   cmailMailedMove
11208         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11209         return TRUE;            /* Allow free viewing  */
11210     }
11211
11212     /* Unregister move to ensure that we don't leave RegisterMove        */
11213     /* with the move registered when the conditions for registering no   */
11214     /* longer hold                                                       */
11215     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11216         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11217         nCmailMovesRegistered --;
11218
11219         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11220           {
11221               free(cmailCommentList[lastLoadGameNumber - 1]);
11222               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11223           }
11224     }
11225
11226     if (cmailOldMove == -1) {
11227         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11228         return FALSE;
11229     }
11230
11231     if (currentMove > cmailOldMove + 1) {
11232         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11233         return FALSE;
11234     }
11235
11236     if (currentMove < cmailOldMove) {
11237         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11238         return FALSE;
11239     }
11240
11241     if (forwardMostMove > currentMove) {
11242         /* Silently truncate extra moves */
11243         TruncateGame();
11244     }
11245
11246     if (   (currentMove == cmailOldMove + 1)
11247         || (   (currentMove == cmailOldMove)
11248             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11249                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11250         if (gameInfo.result != GameUnfinished) {
11251             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11252         }
11253
11254         if (commentList[currentMove] != NULL) {
11255             cmailCommentList[lastLoadGameNumber - 1]
11256               = StrSave(commentList[currentMove]);
11257         }
11258         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11259
11260         if (appData.debugMode)
11261           fprintf(debugFP, "Saving %s for game %d\n",
11262                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11263
11264         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11265
11266         f = fopen(string, "w");
11267         if (appData.oldSaveStyle) {
11268             SaveGameOldStyle(f); /* also closes the file */
11269
11270             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11271             f = fopen(string, "w");
11272             SavePosition(f, 0, NULL); /* also closes the file */
11273         } else {
11274             fprintf(f, "{--------------\n");
11275             PrintPosition(f, currentMove);
11276             fprintf(f, "--------------}\n\n");
11277
11278             SaveGame(f, 0, NULL); /* also closes the file*/
11279         }
11280
11281         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11282         nCmailMovesRegistered ++;
11283     } else if (nCmailGames == 1) {
11284         DisplayError(_("You have not made a move yet"), 0);
11285         return FALSE;
11286     }
11287
11288     return TRUE;
11289 }
11290
11291 void
11292 MailMoveEvent()
11293 {
11294 #if !WIN32
11295     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11296     FILE *commandOutput;
11297     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11298     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11299     int nBuffers;
11300     int i;
11301     int archived;
11302     char *arcDir;
11303
11304     if (! cmailMsgLoaded) {
11305         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11306         return;
11307     }
11308
11309     if (nCmailGames == nCmailResults) {
11310         DisplayError(_("No unfinished games"), 0);
11311         return;
11312     }
11313
11314 #if CMAIL_PROHIBIT_REMAIL
11315     if (cmailMailedMove) {
11316       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);
11317         DisplayError(msg, 0);
11318         return;
11319     }
11320 #endif
11321
11322     if (! (cmailMailedMove || RegisterMove())) return;
11323
11324     if (   cmailMailedMove
11325         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11326       snprintf(string, MSG_SIZ, partCommandString,
11327                appData.debugMode ? " -v" : "", appData.cmailGameName);
11328         commandOutput = popen(string, "r");
11329
11330         if (commandOutput == NULL) {
11331             DisplayError(_("Failed to invoke cmail"), 0);
11332         } else {
11333             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11334                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11335             }
11336             if (nBuffers > 1) {
11337                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11338                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11339                 nBytes = MSG_SIZ - 1;
11340             } else {
11341                 (void) memcpy(msg, buffer, nBytes);
11342             }
11343             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11344
11345             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11346                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11347
11348                 archived = TRUE;
11349                 for (i = 0; i < nCmailGames; i ++) {
11350                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11351                         archived = FALSE;
11352                     }
11353                 }
11354                 if (   archived
11355                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11356                         != NULL)) {
11357                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11358                            arcDir,
11359                            appData.cmailGameName,
11360                            gameInfo.date);
11361                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11362                     cmailMsgLoaded = FALSE;
11363                 }
11364             }
11365
11366             DisplayInformation(msg);
11367             pclose(commandOutput);
11368         }
11369     } else {
11370         if ((*cmailMsg) != '\0') {
11371             DisplayInformation(cmailMsg);
11372         }
11373     }
11374
11375     return;
11376 #endif /* !WIN32 */
11377 }
11378
11379 char *
11380 CmailMsg()
11381 {
11382 #if WIN32
11383     return NULL;
11384 #else
11385     int  prependComma = 0;
11386     char number[5];
11387     char string[MSG_SIZ];       /* Space for game-list */
11388     int  i;
11389
11390     if (!cmailMsgLoaded) return "";
11391
11392     if (cmailMailedMove) {
11393       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11394     } else {
11395         /* Create a list of games left */
11396       snprintf(string, MSG_SIZ, "[");
11397         for (i = 0; i < nCmailGames; i ++) {
11398             if (! (   cmailMoveRegistered[i]
11399                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11400                 if (prependComma) {
11401                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11402                 } else {
11403                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11404                     prependComma = 1;
11405                 }
11406
11407                 strcat(string, number);
11408             }
11409         }
11410         strcat(string, "]");
11411
11412         if (nCmailMovesRegistered + nCmailResults == 0) {
11413             switch (nCmailGames) {
11414               case 1:
11415                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11416                 break;
11417
11418               case 2:
11419                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11420                 break;
11421
11422               default:
11423                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11424                          nCmailGames);
11425                 break;
11426             }
11427         } else {
11428             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11429               case 1:
11430                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11431                          string);
11432                 break;
11433
11434               case 0:
11435                 if (nCmailResults == nCmailGames) {
11436                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11437                 } else {
11438                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11439                 }
11440                 break;
11441
11442               default:
11443                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11444                          string);
11445             }
11446         }
11447     }
11448     return cmailMsg;
11449 #endif /* WIN32 */
11450 }
11451
11452 void
11453 ResetGameEvent()
11454 {
11455     if (gameMode == Training)
11456       SetTrainingModeOff();
11457
11458     Reset(TRUE, TRUE);
11459     cmailMsgLoaded = FALSE;
11460     if (appData.icsActive) {
11461       SendToICS(ics_prefix);
11462       SendToICS("refresh\n");
11463     }
11464 }
11465
11466 void
11467 ExitEvent(status)
11468      int status;
11469 {
11470     exiting++;
11471     if (exiting > 2) {
11472       /* Give up on clean exit */
11473       exit(status);
11474     }
11475     if (exiting > 1) {
11476       /* Keep trying for clean exit */
11477       return;
11478     }
11479
11480     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11481
11482     if (telnetISR != NULL) {
11483       RemoveInputSource(telnetISR);
11484     }
11485     if (icsPR != NoProc) {
11486       DestroyChildProcess(icsPR, TRUE);
11487     }
11488
11489     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11490     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11491
11492     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11493     /* make sure this other one finishes before killing it!                  */
11494     if(endingGame) { int count = 0;
11495         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11496         while(endingGame && count++ < 10) DoSleep(1);
11497         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11498     }
11499
11500     /* Kill off chess programs */
11501     if (first.pr != NoProc) {
11502         ExitAnalyzeMode();
11503
11504         DoSleep( appData.delayBeforeQuit );
11505         SendToProgram("quit\n", &first);
11506         DoSleep( appData.delayAfterQuit );
11507         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11508     }
11509     if (second.pr != NoProc) {
11510         DoSleep( appData.delayBeforeQuit );
11511         SendToProgram("quit\n", &second);
11512         DoSleep( appData.delayAfterQuit );
11513         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11514     }
11515     if (first.isr != NULL) {
11516         RemoveInputSource(first.isr);
11517     }
11518     if (second.isr != NULL) {
11519         RemoveInputSource(second.isr);
11520     }
11521
11522     ShutDownFrontEnd();
11523     exit(status);
11524 }
11525
11526 void
11527 PauseEvent()
11528 {
11529     if (appData.debugMode)
11530         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11531     if (pausing) {
11532         pausing = FALSE;
11533         ModeHighlight();
11534         if (gameMode == MachinePlaysWhite ||
11535             gameMode == MachinePlaysBlack) {
11536             StartClocks();
11537         } else {
11538             DisplayBothClocks();
11539         }
11540         if (gameMode == PlayFromGameFile) {
11541             if (appData.timeDelay >= 0)
11542                 AutoPlayGameLoop();
11543         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11544             Reset(FALSE, TRUE);
11545             SendToICS(ics_prefix);
11546             SendToICS("refresh\n");
11547         } else if (currentMove < forwardMostMove) {
11548             ForwardInner(forwardMostMove);
11549         }
11550         pauseExamInvalid = FALSE;
11551     } else {
11552         switch (gameMode) {
11553           default:
11554             return;
11555           case IcsExamining:
11556             pauseExamForwardMostMove = forwardMostMove;
11557             pauseExamInvalid = FALSE;
11558             /* fall through */
11559           case IcsObserving:
11560           case IcsPlayingWhite:
11561           case IcsPlayingBlack:
11562             pausing = TRUE;
11563             ModeHighlight();
11564             return;
11565           case PlayFromGameFile:
11566             (void) StopLoadGameTimer();
11567             pausing = TRUE;
11568             ModeHighlight();
11569             break;
11570           case BeginningOfGame:
11571             if (appData.icsActive) return;
11572             /* else fall through */
11573           case MachinePlaysWhite:
11574           case MachinePlaysBlack:
11575           case TwoMachinesPlay:
11576             if (forwardMostMove == 0)
11577               return;           /* don't pause if no one has moved */
11578             if ((gameMode == MachinePlaysWhite &&
11579                  !WhiteOnMove(forwardMostMove)) ||
11580                 (gameMode == MachinePlaysBlack &&
11581                  WhiteOnMove(forwardMostMove))) {
11582                 StopClocks();
11583             }
11584             pausing = TRUE;
11585             ModeHighlight();
11586             break;
11587         }
11588     }
11589 }
11590
11591 void
11592 EditCommentEvent()
11593 {
11594     char title[MSG_SIZ];
11595
11596     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11597       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11598     } else {
11599       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11600                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11601                parseList[currentMove - 1]);
11602     }
11603
11604     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11605 }
11606
11607
11608 void
11609 EditTagsEvent()
11610 {
11611     char *tags = PGNTags(&gameInfo);
11612     EditTagsPopUp(tags);
11613     free(tags);
11614 }
11615
11616 void
11617 AnalyzeModeEvent()
11618 {
11619     if (appData.noChessProgram || gameMode == AnalyzeMode)
11620       return;
11621
11622     if (gameMode != AnalyzeFile) {
11623         if (!appData.icsEngineAnalyze) {
11624                EditGameEvent();
11625                if (gameMode != EditGame) return;
11626         }
11627         ResurrectChessProgram();
11628         SendToProgram("analyze\n", &first);
11629         first.analyzing = TRUE;
11630         /*first.maybeThinking = TRUE;*/
11631         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11632         EngineOutputPopUp();
11633     }
11634     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11635     pausing = FALSE;
11636     ModeHighlight();
11637     SetGameInfo();
11638
11639     StartAnalysisClock();
11640     GetTimeMark(&lastNodeCountTime);
11641     lastNodeCount = 0;
11642 }
11643
11644 void
11645 AnalyzeFileEvent()
11646 {
11647     if (appData.noChessProgram || gameMode == AnalyzeFile)
11648       return;
11649
11650     if (gameMode != AnalyzeMode) {
11651         EditGameEvent();
11652         if (gameMode != EditGame) return;
11653         ResurrectChessProgram();
11654         SendToProgram("analyze\n", &first);
11655         first.analyzing = TRUE;
11656         /*first.maybeThinking = TRUE;*/
11657         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11658         EngineOutputPopUp();
11659     }
11660     gameMode = AnalyzeFile;
11661     pausing = FALSE;
11662     ModeHighlight();
11663     SetGameInfo();
11664
11665     StartAnalysisClock();
11666     GetTimeMark(&lastNodeCountTime);
11667     lastNodeCount = 0;
11668 }
11669
11670 void
11671 MachineWhiteEvent()
11672 {
11673     char buf[MSG_SIZ];
11674     char *bookHit = NULL;
11675
11676     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11677       return;
11678
11679
11680     if (gameMode == PlayFromGameFile ||
11681         gameMode == TwoMachinesPlay  ||
11682         gameMode == Training         ||
11683         gameMode == AnalyzeMode      ||
11684         gameMode == EndOfGame)
11685         EditGameEvent();
11686
11687     if (gameMode == EditPosition)
11688         EditPositionDone(TRUE);
11689
11690     if (!WhiteOnMove(currentMove)) {
11691         DisplayError(_("It is not White's turn"), 0);
11692         return;
11693     }
11694
11695     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11696       ExitAnalyzeMode();
11697
11698     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11699         gameMode == AnalyzeFile)
11700         TruncateGame();
11701
11702     ResurrectChessProgram();    /* in case it isn't running */
11703     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11704         gameMode = MachinePlaysWhite;
11705         ResetClocks();
11706     } else
11707     gameMode = MachinePlaysWhite;
11708     pausing = FALSE;
11709     ModeHighlight();
11710     SetGameInfo();
11711     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11712     DisplayTitle(buf);
11713     if (first.sendName) {
11714       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11715       SendToProgram(buf, &first);
11716     }
11717     if (first.sendTime) {
11718       if (first.useColors) {
11719         SendToProgram("black\n", &first); /*gnu kludge*/
11720       }
11721       SendTimeRemaining(&first, TRUE);
11722     }
11723     if (first.useColors) {
11724       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11725     }
11726     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11727     SetMachineThinkingEnables();
11728     first.maybeThinking = TRUE;
11729     StartClocks();
11730     firstMove = FALSE;
11731
11732     if (appData.autoFlipView && !flipView) {
11733       flipView = !flipView;
11734       DrawPosition(FALSE, NULL);
11735       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11736     }
11737
11738     if(bookHit) { // [HGM] book: simulate book reply
11739         static char bookMove[MSG_SIZ]; // a bit generous?
11740
11741         programStats.nodes = programStats.depth = programStats.time =
11742         programStats.score = programStats.got_only_move = 0;
11743         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11744
11745         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11746         strcat(bookMove, bookHit);
11747         HandleMachineMove(bookMove, &first);
11748     }
11749 }
11750
11751 void
11752 MachineBlackEvent()
11753 {
11754   char buf[MSG_SIZ];
11755   char *bookHit = NULL;
11756
11757     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11758         return;
11759
11760
11761     if (gameMode == PlayFromGameFile ||
11762         gameMode == TwoMachinesPlay  ||
11763         gameMode == Training         ||
11764         gameMode == AnalyzeMode      ||
11765         gameMode == EndOfGame)
11766         EditGameEvent();
11767
11768     if (gameMode == EditPosition)
11769         EditPositionDone(TRUE);
11770
11771     if (WhiteOnMove(currentMove)) {
11772         DisplayError(_("It is not Black's turn"), 0);
11773         return;
11774     }
11775
11776     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11777       ExitAnalyzeMode();
11778
11779     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11780         gameMode == AnalyzeFile)
11781         TruncateGame();
11782
11783     ResurrectChessProgram();    /* in case it isn't running */
11784     gameMode = MachinePlaysBlack;
11785     pausing = FALSE;
11786     ModeHighlight();
11787     SetGameInfo();
11788     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11789     DisplayTitle(buf);
11790     if (first.sendName) {
11791       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11792       SendToProgram(buf, &first);
11793     }
11794     if (first.sendTime) {
11795       if (first.useColors) {
11796         SendToProgram("white\n", &first); /*gnu kludge*/
11797       }
11798       SendTimeRemaining(&first, FALSE);
11799     }
11800     if (first.useColors) {
11801       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11802     }
11803     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11804     SetMachineThinkingEnables();
11805     first.maybeThinking = TRUE;
11806     StartClocks();
11807
11808     if (appData.autoFlipView && flipView) {
11809       flipView = !flipView;
11810       DrawPosition(FALSE, NULL);
11811       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11812     }
11813     if(bookHit) { // [HGM] book: simulate book reply
11814         static char bookMove[MSG_SIZ]; // a bit generous?
11815
11816         programStats.nodes = programStats.depth = programStats.time =
11817         programStats.score = programStats.got_only_move = 0;
11818         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11819
11820         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11821         strcat(bookMove, bookHit);
11822         HandleMachineMove(bookMove, &first);
11823     }
11824 }
11825
11826
11827 void
11828 DisplayTwoMachinesTitle()
11829 {
11830     char buf[MSG_SIZ];
11831     if (appData.matchGames > 0) {
11832         if (first.twoMachinesColor[0] == 'w') {
11833           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11834                    gameInfo.white, gameInfo.black,
11835                    first.matchWins, second.matchWins,
11836                    matchGame - 1 - (first.matchWins + second.matchWins));
11837         } else {
11838           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11839                    gameInfo.white, gameInfo.black,
11840                    second.matchWins, first.matchWins,
11841                    matchGame - 1 - (first.matchWins + second.matchWins));
11842         }
11843     } else {
11844       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11845     }
11846     DisplayTitle(buf);
11847 }
11848
11849 void
11850 TwoMachinesEvent P((void))
11851 {
11852     int i;
11853     char buf[MSG_SIZ];
11854     ChessProgramState *onmove;
11855     char *bookHit = NULL;
11856
11857     if (appData.noChessProgram) return;
11858
11859     switch (gameMode) {
11860       case TwoMachinesPlay:
11861         return;
11862       case MachinePlaysWhite:
11863       case MachinePlaysBlack:
11864         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11865             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11866             return;
11867         }
11868         /* fall through */
11869       case BeginningOfGame:
11870       case PlayFromGameFile:
11871       case EndOfGame:
11872         EditGameEvent();
11873         if (gameMode != EditGame) return;
11874         break;
11875       case EditPosition:
11876         EditPositionDone(TRUE);
11877         break;
11878       case AnalyzeMode:
11879       case AnalyzeFile:
11880         ExitAnalyzeMode();
11881         break;
11882       case EditGame:
11883       default:
11884         break;
11885     }
11886
11887 //    forwardMostMove = currentMove;
11888     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11889     ResurrectChessProgram();    /* in case first program isn't running */
11890
11891     if (second.pr == NULL) {
11892         StartChessProgram(&second);
11893         if (second.protocolVersion == 1) {
11894           TwoMachinesEventIfReady();
11895         } else {
11896           /* kludge: allow timeout for initial "feature" command */
11897           FreezeUI();
11898           DisplayMessage("", _("Starting second chess program"));
11899           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11900         }
11901         return;
11902     }
11903     DisplayMessage("", "");
11904     InitChessProgram(&second, FALSE);
11905     SendToProgram("force\n", &second);
11906     if (startedFromSetupPosition) {
11907         SendBoard(&second, backwardMostMove);
11908     if (appData.debugMode) {
11909         fprintf(debugFP, "Two Machines\n");
11910     }
11911     }
11912     for (i = backwardMostMove; i < forwardMostMove; i++) {
11913         SendMoveToProgram(i, &second);
11914     }
11915
11916     gameMode = TwoMachinesPlay;
11917     pausing = FALSE;
11918     ModeHighlight();
11919     SetGameInfo();
11920     DisplayTwoMachinesTitle();
11921     firstMove = TRUE;
11922     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11923         onmove = &first;
11924     } else {
11925         onmove = &second;
11926     }
11927
11928     SendToProgram(first.computerString, &first);
11929     if (first.sendName) {
11930       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11931       SendToProgram(buf, &first);
11932     }
11933     SendToProgram(second.computerString, &second);
11934     if (second.sendName) {
11935       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11936       SendToProgram(buf, &second);
11937     }
11938
11939     ResetClocks();
11940     if (!first.sendTime || !second.sendTime) {
11941         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11942         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11943     }
11944     if (onmove->sendTime) {
11945       if (onmove->useColors) {
11946         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11947       }
11948       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11949     }
11950     if (onmove->useColors) {
11951       SendToProgram(onmove->twoMachinesColor, onmove);
11952     }
11953     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11954 //    SendToProgram("go\n", onmove);
11955     onmove->maybeThinking = TRUE;
11956     SetMachineThinkingEnables();
11957
11958     StartClocks();
11959
11960     if(bookHit) { // [HGM] book: simulate book reply
11961         static char bookMove[MSG_SIZ]; // a bit generous?
11962
11963         programStats.nodes = programStats.depth = programStats.time =
11964         programStats.score = programStats.got_only_move = 0;
11965         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11966
11967         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11968         strcat(bookMove, bookHit);
11969         savedMessage = bookMove; // args for deferred call
11970         savedState = onmove;
11971         ScheduleDelayedEvent(DeferredBookMove, 1);
11972     }
11973 }
11974
11975 void
11976 TrainingEvent()
11977 {
11978     if (gameMode == Training) {
11979       SetTrainingModeOff();
11980       gameMode = PlayFromGameFile;
11981       DisplayMessage("", _("Training mode off"));
11982     } else {
11983       gameMode = Training;
11984       animateTraining = appData.animate;
11985
11986       /* make sure we are not already at the end of the game */
11987       if (currentMove < forwardMostMove) {
11988         SetTrainingModeOn();
11989         DisplayMessage("", _("Training mode on"));
11990       } else {
11991         gameMode = PlayFromGameFile;
11992         DisplayError(_("Already at end of game"), 0);
11993       }
11994     }
11995     ModeHighlight();
11996 }
11997
11998 void
11999 IcsClientEvent()
12000 {
12001     if (!appData.icsActive) return;
12002     switch (gameMode) {
12003       case IcsPlayingWhite:
12004       case IcsPlayingBlack:
12005       case IcsObserving:
12006       case IcsIdle:
12007       case BeginningOfGame:
12008       case IcsExamining:
12009         return;
12010
12011       case EditGame:
12012         break;
12013
12014       case EditPosition:
12015         EditPositionDone(TRUE);
12016         break;
12017
12018       case AnalyzeMode:
12019       case AnalyzeFile:
12020         ExitAnalyzeMode();
12021         break;
12022
12023       default:
12024         EditGameEvent();
12025         break;
12026     }
12027
12028     gameMode = IcsIdle;
12029     ModeHighlight();
12030     return;
12031 }
12032
12033
12034 void
12035 EditGameEvent()
12036 {
12037     int i;
12038
12039     switch (gameMode) {
12040       case Training:
12041         SetTrainingModeOff();
12042         break;
12043       case MachinePlaysWhite:
12044       case MachinePlaysBlack:
12045       case BeginningOfGame:
12046         SendToProgram("force\n", &first);
12047         SetUserThinkingEnables();
12048         break;
12049       case PlayFromGameFile:
12050         (void) StopLoadGameTimer();
12051         if (gameFileFP != NULL) {
12052             gameFileFP = NULL;
12053         }
12054         break;
12055       case EditPosition:
12056         EditPositionDone(TRUE);
12057         break;
12058       case AnalyzeMode:
12059       case AnalyzeFile:
12060         ExitAnalyzeMode();
12061         SendToProgram("force\n", &first);
12062         break;
12063       case TwoMachinesPlay:
12064         GameEnds(EndOfFile, NULL, GE_PLAYER);
12065         ResurrectChessProgram();
12066         SetUserThinkingEnables();
12067         break;
12068       case EndOfGame:
12069         ResurrectChessProgram();
12070         break;
12071       case IcsPlayingBlack:
12072       case IcsPlayingWhite:
12073         DisplayError(_("Warning: You are still playing a game"), 0);
12074         break;
12075       case IcsObserving:
12076         DisplayError(_("Warning: You are still observing a game"), 0);
12077         break;
12078       case IcsExamining:
12079         DisplayError(_("Warning: You are still examining a game"), 0);
12080         break;
12081       case IcsIdle:
12082         break;
12083       case EditGame:
12084       default:
12085         return;
12086     }
12087
12088     pausing = FALSE;
12089     StopClocks();
12090     first.offeredDraw = second.offeredDraw = 0;
12091
12092     if (gameMode == PlayFromGameFile) {
12093         whiteTimeRemaining = timeRemaining[0][currentMove];
12094         blackTimeRemaining = timeRemaining[1][currentMove];
12095         DisplayTitle("");
12096     }
12097
12098     if (gameMode == MachinePlaysWhite ||
12099         gameMode == MachinePlaysBlack ||
12100         gameMode == TwoMachinesPlay ||
12101         gameMode == EndOfGame) {
12102         i = forwardMostMove;
12103         while (i > currentMove) {
12104             SendToProgram("undo\n", &first);
12105             i--;
12106         }
12107         whiteTimeRemaining = timeRemaining[0][currentMove];
12108         blackTimeRemaining = timeRemaining[1][currentMove];
12109         DisplayBothClocks();
12110         if (whiteFlag || blackFlag) {
12111             whiteFlag = blackFlag = 0;
12112         }
12113         DisplayTitle("");
12114     }
12115
12116     gameMode = EditGame;
12117     ModeHighlight();
12118     SetGameInfo();
12119 }
12120
12121
12122 void
12123 EditPositionEvent()
12124 {
12125     if (gameMode == EditPosition) {
12126         EditGameEvent();
12127         return;
12128     }
12129
12130     EditGameEvent();
12131     if (gameMode != EditGame) return;
12132
12133     gameMode = EditPosition;
12134     ModeHighlight();
12135     SetGameInfo();
12136     if (currentMove > 0)
12137       CopyBoard(boards[0], boards[currentMove]);
12138
12139     blackPlaysFirst = !WhiteOnMove(currentMove);
12140     ResetClocks();
12141     currentMove = forwardMostMove = backwardMostMove = 0;
12142     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12143     DisplayMove(-1);
12144 }
12145
12146 void
12147 ExitAnalyzeMode()
12148 {
12149     /* [DM] icsEngineAnalyze - possible call from other functions */
12150     if (appData.icsEngineAnalyze) {
12151         appData.icsEngineAnalyze = FALSE;
12152
12153         DisplayMessage("",_("Close ICS engine analyze..."));
12154     }
12155     if (first.analysisSupport && first.analyzing) {
12156       SendToProgram("exit\n", &first);
12157       first.analyzing = FALSE;
12158     }
12159     thinkOutput[0] = NULLCHAR;
12160 }
12161
12162 void
12163 EditPositionDone(Boolean fakeRights)
12164 {
12165     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12166
12167     startedFromSetupPosition = TRUE;
12168     InitChessProgram(&first, FALSE);
12169     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12170       boards[0][EP_STATUS] = EP_NONE;
12171       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12172     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12173         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12174         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12175       } else boards[0][CASTLING][2] = NoRights;
12176     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12177         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12178         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12179       } else boards[0][CASTLING][5] = NoRights;
12180     }
12181     SendToProgram("force\n", &first);
12182     if (blackPlaysFirst) {
12183         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12184         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12185         currentMove = forwardMostMove = backwardMostMove = 1;
12186         CopyBoard(boards[1], boards[0]);
12187     } else {
12188         currentMove = forwardMostMove = backwardMostMove = 0;
12189     }
12190     SendBoard(&first, forwardMostMove);
12191     if (appData.debugMode) {
12192         fprintf(debugFP, "EditPosDone\n");
12193     }
12194     DisplayTitle("");
12195     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12196     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12197     gameMode = EditGame;
12198     ModeHighlight();
12199     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12200     ClearHighlights(); /* [AS] */
12201 }
12202
12203 /* Pause for `ms' milliseconds */
12204 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12205 void
12206 TimeDelay(ms)
12207      long ms;
12208 {
12209     TimeMark m1, m2;
12210
12211     GetTimeMark(&m1);
12212     do {
12213         GetTimeMark(&m2);
12214     } while (SubtractTimeMarks(&m2, &m1) < ms);
12215 }
12216
12217 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12218 void
12219 SendMultiLineToICS(buf)
12220      char *buf;
12221 {
12222     char temp[MSG_SIZ+1], *p;
12223     int len;
12224
12225     len = strlen(buf);
12226     if (len > MSG_SIZ)
12227       len = MSG_SIZ;
12228
12229     strncpy(temp, buf, len);
12230     temp[len] = 0;
12231
12232     p = temp;
12233     while (*p) {
12234         if (*p == '\n' || *p == '\r')
12235           *p = ' ';
12236         ++p;
12237     }
12238
12239     strcat(temp, "\n");
12240     SendToICS(temp);
12241     SendToPlayer(temp, strlen(temp));
12242 }
12243
12244 void
12245 SetWhiteToPlayEvent()
12246 {
12247     if (gameMode == EditPosition) {
12248         blackPlaysFirst = FALSE;
12249         DisplayBothClocks();    /* works because currentMove is 0 */
12250     } else if (gameMode == IcsExamining) {
12251         SendToICS(ics_prefix);
12252         SendToICS("tomove white\n");
12253     }
12254 }
12255
12256 void
12257 SetBlackToPlayEvent()
12258 {
12259     if (gameMode == EditPosition) {
12260         blackPlaysFirst = TRUE;
12261         currentMove = 1;        /* kludge */
12262         DisplayBothClocks();
12263         currentMove = 0;
12264     } else if (gameMode == IcsExamining) {
12265         SendToICS(ics_prefix);
12266         SendToICS("tomove black\n");
12267     }
12268 }
12269
12270 void
12271 EditPositionMenuEvent(selection, x, y)
12272      ChessSquare selection;
12273      int x, y;
12274 {
12275     char buf[MSG_SIZ];
12276     ChessSquare piece = boards[0][y][x];
12277
12278     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12279
12280     switch (selection) {
12281       case ClearBoard:
12282         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12283             SendToICS(ics_prefix);
12284             SendToICS("bsetup clear\n");
12285         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12286             SendToICS(ics_prefix);
12287             SendToICS("clearboard\n");
12288         } else {
12289             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12290                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12291                 for (y = 0; y < BOARD_HEIGHT; y++) {
12292                     if (gameMode == IcsExamining) {
12293                         if (boards[currentMove][y][x] != EmptySquare) {
12294                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12295                                     AAA + x, ONE + y);
12296                             SendToICS(buf);
12297                         }
12298                     } else {
12299                         boards[0][y][x] = p;
12300                     }
12301                 }
12302             }
12303         }
12304         if (gameMode == EditPosition) {
12305             DrawPosition(FALSE, boards[0]);
12306         }
12307         break;
12308
12309       case WhitePlay:
12310         SetWhiteToPlayEvent();
12311         break;
12312
12313       case BlackPlay:
12314         SetBlackToPlayEvent();
12315         break;
12316
12317       case EmptySquare:
12318         if (gameMode == IcsExamining) {
12319             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12320             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12321             SendToICS(buf);
12322         } else {
12323             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12324                 if(x == BOARD_LEFT-2) {
12325                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12326                     boards[0][y][1] = 0;
12327                 } else
12328                 if(x == BOARD_RGHT+1) {
12329                     if(y >= gameInfo.holdingsSize) break;
12330                     boards[0][y][BOARD_WIDTH-2] = 0;
12331                 } else break;
12332             }
12333             boards[0][y][x] = EmptySquare;
12334             DrawPosition(FALSE, boards[0]);
12335         }
12336         break;
12337
12338       case PromotePiece:
12339         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12340            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12341             selection = (ChessSquare) (PROMOTED piece);
12342         } else if(piece == EmptySquare) selection = WhiteSilver;
12343         else selection = (ChessSquare)((int)piece - 1);
12344         goto defaultlabel;
12345
12346       case DemotePiece:
12347         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12348            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12349             selection = (ChessSquare) (DEMOTED piece);
12350         } else if(piece == EmptySquare) selection = BlackSilver;
12351         else selection = (ChessSquare)((int)piece + 1);
12352         goto defaultlabel;
12353
12354       case WhiteQueen:
12355       case BlackQueen:
12356         if(gameInfo.variant == VariantShatranj ||
12357            gameInfo.variant == VariantXiangqi  ||
12358            gameInfo.variant == VariantCourier  ||
12359            gameInfo.variant == VariantMakruk     )
12360             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12361         goto defaultlabel;
12362
12363       case WhiteKing:
12364       case BlackKing:
12365         if(gameInfo.variant == VariantXiangqi)
12366             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12367         if(gameInfo.variant == VariantKnightmate)
12368             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12369       default:
12370         defaultlabel:
12371         if (gameMode == IcsExamining) {
12372             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12373             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12374                      PieceToChar(selection), AAA + x, ONE + y);
12375             SendToICS(buf);
12376         } else {
12377             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12378                 int n;
12379                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12380                     n = PieceToNumber(selection - BlackPawn);
12381                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12382                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12383                     boards[0][BOARD_HEIGHT-1-n][1]++;
12384                 } else
12385                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12386                     n = PieceToNumber(selection);
12387                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12388                     boards[0][n][BOARD_WIDTH-1] = selection;
12389                     boards[0][n][BOARD_WIDTH-2]++;
12390                 }
12391             } else
12392             boards[0][y][x] = selection;
12393             DrawPosition(TRUE, boards[0]);
12394         }
12395         break;
12396     }
12397 }
12398
12399
12400 void
12401 DropMenuEvent(selection, x, y)
12402      ChessSquare selection;
12403      int x, y;
12404 {
12405     ChessMove moveType;
12406
12407     switch (gameMode) {
12408       case IcsPlayingWhite:
12409       case MachinePlaysBlack:
12410         if (!WhiteOnMove(currentMove)) {
12411             DisplayMoveError(_("It is Black's turn"));
12412             return;
12413         }
12414         moveType = WhiteDrop;
12415         break;
12416       case IcsPlayingBlack:
12417       case MachinePlaysWhite:
12418         if (WhiteOnMove(currentMove)) {
12419             DisplayMoveError(_("It is White's turn"));
12420             return;
12421         }
12422         moveType = BlackDrop;
12423         break;
12424       case EditGame:
12425         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12426         break;
12427       default:
12428         return;
12429     }
12430
12431     if (moveType == BlackDrop && selection < BlackPawn) {
12432       selection = (ChessSquare) ((int) selection
12433                                  + (int) BlackPawn - (int) WhitePawn);
12434     }
12435     if (boards[currentMove][y][x] != EmptySquare) {
12436         DisplayMoveError(_("That square is occupied"));
12437         return;
12438     }
12439
12440     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12441 }
12442
12443 void
12444 AcceptEvent()
12445 {
12446     /* Accept a pending offer of any kind from opponent */
12447
12448     if (appData.icsActive) {
12449         SendToICS(ics_prefix);
12450         SendToICS("accept\n");
12451     } else if (cmailMsgLoaded) {
12452         if (currentMove == cmailOldMove &&
12453             commentList[cmailOldMove] != NULL &&
12454             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12455                    "Black offers a draw" : "White offers a draw")) {
12456             TruncateGame();
12457             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12458             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12459         } else {
12460             DisplayError(_("There is no pending offer on this move"), 0);
12461             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12462         }
12463     } else {
12464         /* Not used for offers from chess program */
12465     }
12466 }
12467
12468 void
12469 DeclineEvent()
12470 {
12471     /* Decline a pending offer of any kind from opponent */
12472
12473     if (appData.icsActive) {
12474         SendToICS(ics_prefix);
12475         SendToICS("decline\n");
12476     } else if (cmailMsgLoaded) {
12477         if (currentMove == cmailOldMove &&
12478             commentList[cmailOldMove] != NULL &&
12479             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12480                    "Black offers a draw" : "White offers a draw")) {
12481 #ifdef NOTDEF
12482             AppendComment(cmailOldMove, "Draw declined", TRUE);
12483             DisplayComment(cmailOldMove - 1, "Draw declined");
12484 #endif /*NOTDEF*/
12485         } else {
12486             DisplayError(_("There is no pending offer on this move"), 0);
12487         }
12488     } else {
12489         /* Not used for offers from chess program */
12490     }
12491 }
12492
12493 void
12494 RematchEvent()
12495 {
12496     /* Issue ICS rematch command */
12497     if (appData.icsActive) {
12498         SendToICS(ics_prefix);
12499         SendToICS("rematch\n");
12500     }
12501 }
12502
12503 void
12504 CallFlagEvent()
12505 {
12506     /* Call your opponent's flag (claim a win on time) */
12507     if (appData.icsActive) {
12508         SendToICS(ics_prefix);
12509         SendToICS("flag\n");
12510     } else {
12511         switch (gameMode) {
12512           default:
12513             return;
12514           case MachinePlaysWhite:
12515             if (whiteFlag) {
12516                 if (blackFlag)
12517                   GameEnds(GameIsDrawn, "Both players ran out of time",
12518                            GE_PLAYER);
12519                 else
12520                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12521             } else {
12522                 DisplayError(_("Your opponent is not out of time"), 0);
12523             }
12524             break;
12525           case MachinePlaysBlack:
12526             if (blackFlag) {
12527                 if (whiteFlag)
12528                   GameEnds(GameIsDrawn, "Both players ran out of time",
12529                            GE_PLAYER);
12530                 else
12531                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12532             } else {
12533                 DisplayError(_("Your opponent is not out of time"), 0);
12534             }
12535             break;
12536         }
12537     }
12538 }
12539
12540 void
12541 DrawEvent()
12542 {
12543     /* Offer draw or accept pending draw offer from opponent */
12544
12545     if (appData.icsActive) {
12546         /* Note: tournament rules require draw offers to be
12547            made after you make your move but before you punch
12548            your clock.  Currently ICS doesn't let you do that;
12549            instead, you immediately punch your clock after making
12550            a move, but you can offer a draw at any time. */
12551
12552         SendToICS(ics_prefix);
12553         SendToICS("draw\n");
12554         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12555     } else if (cmailMsgLoaded) {
12556         if (currentMove == cmailOldMove &&
12557             commentList[cmailOldMove] != NULL &&
12558             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12559                    "Black offers a draw" : "White offers a draw")) {
12560             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12561             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12562         } else if (currentMove == cmailOldMove + 1) {
12563             char *offer = WhiteOnMove(cmailOldMove) ?
12564               "White offers a draw" : "Black offers a draw";
12565             AppendComment(currentMove, offer, TRUE);
12566             DisplayComment(currentMove - 1, offer);
12567             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12568         } else {
12569             DisplayError(_("You must make your move before offering a draw"), 0);
12570             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12571         }
12572     } else if (first.offeredDraw) {
12573         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12574     } else {
12575         if (first.sendDrawOffers) {
12576             SendToProgram("draw\n", &first);
12577             userOfferedDraw = TRUE;
12578         }
12579     }
12580 }
12581
12582 void
12583 AdjournEvent()
12584 {
12585     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12586
12587     if (appData.icsActive) {
12588         SendToICS(ics_prefix);
12589         SendToICS("adjourn\n");
12590     } else {
12591         /* Currently GNU Chess doesn't offer or accept Adjourns */
12592     }
12593 }
12594
12595
12596 void
12597 AbortEvent()
12598 {
12599     /* Offer Abort or accept pending Abort offer from opponent */
12600
12601     if (appData.icsActive) {
12602         SendToICS(ics_prefix);
12603         SendToICS("abort\n");
12604     } else {
12605         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12606     }
12607 }
12608
12609 void
12610 ResignEvent()
12611 {
12612     /* Resign.  You can do this even if it's not your turn. */
12613
12614     if (appData.icsActive) {
12615         SendToICS(ics_prefix);
12616         SendToICS("resign\n");
12617     } else {
12618         switch (gameMode) {
12619           case MachinePlaysWhite:
12620             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12621             break;
12622           case MachinePlaysBlack:
12623             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12624             break;
12625           case EditGame:
12626             if (cmailMsgLoaded) {
12627                 TruncateGame();
12628                 if (WhiteOnMove(cmailOldMove)) {
12629                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12630                 } else {
12631                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12632                 }
12633                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12634             }
12635             break;
12636           default:
12637             break;
12638         }
12639     }
12640 }
12641
12642
12643 void
12644 StopObservingEvent()
12645 {
12646     /* Stop observing current games */
12647     SendToICS(ics_prefix);
12648     SendToICS("unobserve\n");
12649 }
12650
12651 void
12652 StopExaminingEvent()
12653 {
12654     /* Stop observing current game */
12655     SendToICS(ics_prefix);
12656     SendToICS("unexamine\n");
12657 }
12658
12659 void
12660 ForwardInner(target)
12661      int target;
12662 {
12663     int limit;
12664
12665     if (appData.debugMode)
12666         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12667                 target, currentMove, forwardMostMove);
12668
12669     if (gameMode == EditPosition)
12670       return;
12671
12672     if (gameMode == PlayFromGameFile && !pausing)
12673       PauseEvent();
12674
12675     if (gameMode == IcsExamining && pausing)
12676       limit = pauseExamForwardMostMove;
12677     else
12678       limit = forwardMostMove;
12679
12680     if (target > limit) target = limit;
12681
12682     if (target > 0 && moveList[target - 1][0]) {
12683         int fromX, fromY, toX, toY;
12684         toX = moveList[target - 1][2] - AAA;
12685         toY = moveList[target - 1][3] - ONE;
12686         if (moveList[target - 1][1] == '@') {
12687             if (appData.highlightLastMove) {
12688                 SetHighlights(-1, -1, toX, toY);
12689             }
12690         } else {
12691             fromX = moveList[target - 1][0] - AAA;
12692             fromY = moveList[target - 1][1] - ONE;
12693             if (target == currentMove + 1) {
12694                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12695             }
12696             if (appData.highlightLastMove) {
12697                 SetHighlights(fromX, fromY, toX, toY);
12698             }
12699         }
12700     }
12701     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12702         gameMode == Training || gameMode == PlayFromGameFile ||
12703         gameMode == AnalyzeFile) {
12704         while (currentMove < target) {
12705             SendMoveToProgram(currentMove++, &first);
12706         }
12707     } else {
12708         currentMove = target;
12709     }
12710
12711     if (gameMode == EditGame || gameMode == EndOfGame) {
12712         whiteTimeRemaining = timeRemaining[0][currentMove];
12713         blackTimeRemaining = timeRemaining[1][currentMove];
12714     }
12715     DisplayBothClocks();
12716     DisplayMove(currentMove - 1);
12717     DrawPosition(FALSE, boards[currentMove]);
12718     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12719     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12720         DisplayComment(currentMove - 1, commentList[currentMove]);
12721     }
12722 }
12723
12724
12725 void
12726 ForwardEvent()
12727 {
12728     if (gameMode == IcsExamining && !pausing) {
12729         SendToICS(ics_prefix);
12730         SendToICS("forward\n");
12731     } else {
12732         ForwardInner(currentMove + 1);
12733     }
12734 }
12735
12736 void
12737 ToEndEvent()
12738 {
12739     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12740         /* to optimze, we temporarily turn off analysis mode while we feed
12741          * the remaining moves to the engine. Otherwise we get analysis output
12742          * after each move.
12743          */
12744         if (first.analysisSupport) {
12745           SendToProgram("exit\nforce\n", &first);
12746           first.analyzing = FALSE;
12747         }
12748     }
12749
12750     if (gameMode == IcsExamining && !pausing) {
12751         SendToICS(ics_prefix);
12752         SendToICS("forward 999999\n");
12753     } else {
12754         ForwardInner(forwardMostMove);
12755     }
12756
12757     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12758         /* we have fed all the moves, so reactivate analysis mode */
12759         SendToProgram("analyze\n", &first);
12760         first.analyzing = TRUE;
12761         /*first.maybeThinking = TRUE;*/
12762         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12763     }
12764 }
12765
12766 void
12767 BackwardInner(target)
12768      int target;
12769 {
12770     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12771
12772     if (appData.debugMode)
12773         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12774                 target, currentMove, forwardMostMove);
12775
12776     if (gameMode == EditPosition) return;
12777     if (currentMove <= backwardMostMove) {
12778         ClearHighlights();
12779         DrawPosition(full_redraw, boards[currentMove]);
12780         return;
12781     }
12782     if (gameMode == PlayFromGameFile && !pausing)
12783       PauseEvent();
12784
12785     if (moveList[target][0]) {
12786         int fromX, fromY, toX, toY;
12787         toX = moveList[target][2] - AAA;
12788         toY = moveList[target][3] - ONE;
12789         if (moveList[target][1] == '@') {
12790             if (appData.highlightLastMove) {
12791                 SetHighlights(-1, -1, toX, toY);
12792             }
12793         } else {
12794             fromX = moveList[target][0] - AAA;
12795             fromY = moveList[target][1] - ONE;
12796             if (target == currentMove - 1) {
12797                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12798             }
12799             if (appData.highlightLastMove) {
12800                 SetHighlights(fromX, fromY, toX, toY);
12801             }
12802         }
12803     }
12804     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12805         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12806         while (currentMove > target) {
12807             SendToProgram("undo\n", &first);
12808             currentMove--;
12809         }
12810     } else {
12811         currentMove = target;
12812     }
12813
12814     if (gameMode == EditGame || gameMode == EndOfGame) {
12815         whiteTimeRemaining = timeRemaining[0][currentMove];
12816         blackTimeRemaining = timeRemaining[1][currentMove];
12817     }
12818     DisplayBothClocks();
12819     DisplayMove(currentMove - 1);
12820     DrawPosition(full_redraw, boards[currentMove]);
12821     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12822     // [HGM] PV info: routine tests if comment empty
12823     DisplayComment(currentMove - 1, commentList[currentMove]);
12824 }
12825
12826 void
12827 BackwardEvent()
12828 {
12829     if (gameMode == IcsExamining && !pausing) {
12830         SendToICS(ics_prefix);
12831         SendToICS("backward\n");
12832     } else {
12833         BackwardInner(currentMove - 1);
12834     }
12835 }
12836
12837 void
12838 ToStartEvent()
12839 {
12840     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12841         /* to optimize, we temporarily turn off analysis mode while we undo
12842          * all the moves. Otherwise we get analysis output after each undo.
12843          */
12844         if (first.analysisSupport) {
12845           SendToProgram("exit\nforce\n", &first);
12846           first.analyzing = FALSE;
12847         }
12848     }
12849
12850     if (gameMode == IcsExamining && !pausing) {
12851         SendToICS(ics_prefix);
12852         SendToICS("backward 999999\n");
12853     } else {
12854         BackwardInner(backwardMostMove);
12855     }
12856
12857     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12858         /* we have fed all the moves, so reactivate analysis mode */
12859         SendToProgram("analyze\n", &first);
12860         first.analyzing = TRUE;
12861         /*first.maybeThinking = TRUE;*/
12862         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12863     }
12864 }
12865
12866 void
12867 ToNrEvent(int to)
12868 {
12869   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12870   if (to >= forwardMostMove) to = forwardMostMove;
12871   if (to <= backwardMostMove) to = backwardMostMove;
12872   if (to < currentMove) {
12873     BackwardInner(to);
12874   } else {
12875     ForwardInner(to);
12876   }
12877 }
12878
12879 void
12880 RevertEvent(Boolean annotate)
12881 {
12882     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12883         return;
12884     }
12885     if (gameMode != IcsExamining) {
12886         DisplayError(_("You are not examining a game"), 0);
12887         return;
12888     }
12889     if (pausing) {
12890         DisplayError(_("You can't revert while pausing"), 0);
12891         return;
12892     }
12893     SendToICS(ics_prefix);
12894     SendToICS("revert\n");
12895 }
12896
12897 void
12898 RetractMoveEvent()
12899 {
12900     switch (gameMode) {
12901       case MachinePlaysWhite:
12902       case MachinePlaysBlack:
12903         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12904             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12905             return;
12906         }
12907         if (forwardMostMove < 2) return;
12908         currentMove = forwardMostMove = forwardMostMove - 2;
12909         whiteTimeRemaining = timeRemaining[0][currentMove];
12910         blackTimeRemaining = timeRemaining[1][currentMove];
12911         DisplayBothClocks();
12912         DisplayMove(currentMove - 1);
12913         ClearHighlights();/*!! could figure this out*/
12914         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12915         SendToProgram("remove\n", &first);
12916         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12917         break;
12918
12919       case BeginningOfGame:
12920       default:
12921         break;
12922
12923       case IcsPlayingWhite:
12924       case IcsPlayingBlack:
12925         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12926             SendToICS(ics_prefix);
12927             SendToICS("takeback 2\n");
12928         } else {
12929             SendToICS(ics_prefix);
12930             SendToICS("takeback 1\n");
12931         }
12932         break;
12933     }
12934 }
12935
12936 void
12937 MoveNowEvent()
12938 {
12939     ChessProgramState *cps;
12940
12941     switch (gameMode) {
12942       case MachinePlaysWhite:
12943         if (!WhiteOnMove(forwardMostMove)) {
12944             DisplayError(_("It is your turn"), 0);
12945             return;
12946         }
12947         cps = &first;
12948         break;
12949       case MachinePlaysBlack:
12950         if (WhiteOnMove(forwardMostMove)) {
12951             DisplayError(_("It is your turn"), 0);
12952             return;
12953         }
12954         cps = &first;
12955         break;
12956       case TwoMachinesPlay:
12957         if (WhiteOnMove(forwardMostMove) ==
12958             (first.twoMachinesColor[0] == 'w')) {
12959             cps = &first;
12960         } else {
12961             cps = &second;
12962         }
12963         break;
12964       case BeginningOfGame:
12965       default:
12966         return;
12967     }
12968     SendToProgram("?\n", cps);
12969 }
12970
12971 void
12972 TruncateGameEvent()
12973 {
12974     EditGameEvent();
12975     if (gameMode != EditGame) return;
12976     TruncateGame();
12977 }
12978
12979 void
12980 TruncateGame()
12981 {
12982     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12983     if (forwardMostMove > currentMove) {
12984         if (gameInfo.resultDetails != NULL) {
12985             free(gameInfo.resultDetails);
12986             gameInfo.resultDetails = NULL;
12987             gameInfo.result = GameUnfinished;
12988         }
12989         forwardMostMove = currentMove;
12990         HistorySet(parseList, backwardMostMove, forwardMostMove,
12991                    currentMove-1);
12992     }
12993 }
12994
12995 void
12996 HintEvent()
12997 {
12998     if (appData.noChessProgram) return;
12999     switch (gameMode) {
13000       case MachinePlaysWhite:
13001         if (WhiteOnMove(forwardMostMove)) {
13002             DisplayError(_("Wait until your turn"), 0);
13003             return;
13004         }
13005         break;
13006       case BeginningOfGame:
13007       case MachinePlaysBlack:
13008         if (!WhiteOnMove(forwardMostMove)) {
13009             DisplayError(_("Wait until your turn"), 0);
13010             return;
13011         }
13012         break;
13013       default:
13014         DisplayError(_("No hint available"), 0);
13015         return;
13016     }
13017     SendToProgram("hint\n", &first);
13018     hintRequested = TRUE;
13019 }
13020
13021 void
13022 BookEvent()
13023 {
13024     if (appData.noChessProgram) return;
13025     switch (gameMode) {
13026       case MachinePlaysWhite:
13027         if (WhiteOnMove(forwardMostMove)) {
13028             DisplayError(_("Wait until your turn"), 0);
13029             return;
13030         }
13031         break;
13032       case BeginningOfGame:
13033       case MachinePlaysBlack:
13034         if (!WhiteOnMove(forwardMostMove)) {
13035             DisplayError(_("Wait until your turn"), 0);
13036             return;
13037         }
13038         break;
13039       case EditPosition:
13040         EditPositionDone(TRUE);
13041         break;
13042       case TwoMachinesPlay:
13043         return;
13044       default:
13045         break;
13046     }
13047     SendToProgram("bk\n", &first);
13048     bookOutput[0] = NULLCHAR;
13049     bookRequested = TRUE;
13050 }
13051
13052 void
13053 AboutGameEvent()
13054 {
13055     char *tags = PGNTags(&gameInfo);
13056     TagsPopUp(tags, CmailMsg());
13057     free(tags);
13058 }
13059
13060 /* end button procedures */
13061
13062 void
13063 PrintPosition(fp, move)
13064      FILE *fp;
13065      int move;
13066 {
13067     int i, j;
13068
13069     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13070         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13071             char c = PieceToChar(boards[move][i][j]);
13072             fputc(c == 'x' ? '.' : c, fp);
13073             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13074         }
13075     }
13076     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13077       fprintf(fp, "white to play\n");
13078     else
13079       fprintf(fp, "black to play\n");
13080 }
13081
13082 void
13083 PrintOpponents(fp)
13084      FILE *fp;
13085 {
13086     if (gameInfo.white != NULL) {
13087         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13088     } else {
13089         fprintf(fp, "\n");
13090     }
13091 }
13092
13093 /* Find last component of program's own name, using some heuristics */
13094 void
13095 TidyProgramName(prog, host, buf)
13096      char *prog, *host, buf[MSG_SIZ];
13097 {
13098     char *p, *q;
13099     int local = (strcmp(host, "localhost") == 0);
13100     while (!local && (p = strchr(prog, ';')) != NULL) {
13101         p++;
13102         while (*p == ' ') p++;
13103         prog = p;
13104     }
13105     if (*prog == '"' || *prog == '\'') {
13106         q = strchr(prog + 1, *prog);
13107     } else {
13108         q = strchr(prog, ' ');
13109     }
13110     if (q == NULL) q = prog + strlen(prog);
13111     p = q;
13112     while (p >= prog && *p != '/' && *p != '\\') p--;
13113     p++;
13114     if(p == prog && *p == '"') p++;
13115     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13116     memcpy(buf, p, q - p);
13117     buf[q - p] = NULLCHAR;
13118     if (!local) {
13119         strcat(buf, "@");
13120         strcat(buf, host);
13121     }
13122 }
13123
13124 char *
13125 TimeControlTagValue()
13126 {
13127     char buf[MSG_SIZ];
13128     if (!appData.clockMode) {
13129       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13130     } else if (movesPerSession > 0) {
13131       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13132     } else if (timeIncrement == 0) {
13133       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13134     } else {
13135       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13136     }
13137     return StrSave(buf);
13138 }
13139
13140 void
13141 SetGameInfo()
13142 {
13143     /* This routine is used only for certain modes */
13144     VariantClass v = gameInfo.variant;
13145     ChessMove r = GameUnfinished;
13146     char *p = NULL;
13147
13148     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13149         r = gameInfo.result;
13150         p = gameInfo.resultDetails;
13151         gameInfo.resultDetails = NULL;
13152     }
13153     ClearGameInfo(&gameInfo);
13154     gameInfo.variant = v;
13155
13156     switch (gameMode) {
13157       case MachinePlaysWhite:
13158         gameInfo.event = StrSave( appData.pgnEventHeader );
13159         gameInfo.site = StrSave(HostName());
13160         gameInfo.date = PGNDate();
13161         gameInfo.round = StrSave("-");
13162         gameInfo.white = StrSave(first.tidy);
13163         gameInfo.black = StrSave(UserName());
13164         gameInfo.timeControl = TimeControlTagValue();
13165         break;
13166
13167       case MachinePlaysBlack:
13168         gameInfo.event = StrSave( appData.pgnEventHeader );
13169         gameInfo.site = StrSave(HostName());
13170         gameInfo.date = PGNDate();
13171         gameInfo.round = StrSave("-");
13172         gameInfo.white = StrSave(UserName());
13173         gameInfo.black = StrSave(first.tidy);
13174         gameInfo.timeControl = TimeControlTagValue();
13175         break;
13176
13177       case TwoMachinesPlay:
13178         gameInfo.event = StrSave( appData.pgnEventHeader );
13179         gameInfo.site = StrSave(HostName());
13180         gameInfo.date = PGNDate();
13181         if (matchGame > 0) {
13182             char buf[MSG_SIZ];
13183             snprintf(buf, MSG_SIZ, "%d", matchGame);
13184             gameInfo.round = StrSave(buf);
13185         } else {
13186             gameInfo.round = StrSave("-");
13187         }
13188         if (first.twoMachinesColor[0] == 'w') {
13189             gameInfo.white = StrSave(first.tidy);
13190             gameInfo.black = StrSave(second.tidy);
13191         } else {
13192             gameInfo.white = StrSave(second.tidy);
13193             gameInfo.black = StrSave(first.tidy);
13194         }
13195         gameInfo.timeControl = TimeControlTagValue();
13196         break;
13197
13198       case EditGame:
13199         gameInfo.event = StrSave("Edited game");
13200         gameInfo.site = StrSave(HostName());
13201         gameInfo.date = PGNDate();
13202         gameInfo.round = StrSave("-");
13203         gameInfo.white = StrSave("-");
13204         gameInfo.black = StrSave("-");
13205         gameInfo.result = r;
13206         gameInfo.resultDetails = p;
13207         break;
13208
13209       case EditPosition:
13210         gameInfo.event = StrSave("Edited position");
13211         gameInfo.site = StrSave(HostName());
13212         gameInfo.date = PGNDate();
13213         gameInfo.round = StrSave("-");
13214         gameInfo.white = StrSave("-");
13215         gameInfo.black = StrSave("-");
13216         break;
13217
13218       case IcsPlayingWhite:
13219       case IcsPlayingBlack:
13220       case IcsObserving:
13221       case IcsExamining:
13222         break;
13223
13224       case PlayFromGameFile:
13225         gameInfo.event = StrSave("Game from non-PGN file");
13226         gameInfo.site = StrSave(HostName());
13227         gameInfo.date = PGNDate();
13228         gameInfo.round = StrSave("-");
13229         gameInfo.white = StrSave("?");
13230         gameInfo.black = StrSave("?");
13231         break;
13232
13233       default:
13234         break;
13235     }
13236 }
13237
13238 void
13239 ReplaceComment(index, text)
13240      int index;
13241      char *text;
13242 {
13243     int len;
13244
13245     while (*text == '\n') text++;
13246     len = strlen(text);
13247     while (len > 0 && text[len - 1] == '\n') len--;
13248
13249     if (commentList[index] != NULL)
13250       free(commentList[index]);
13251
13252     if (len == 0) {
13253         commentList[index] = NULL;
13254         return;
13255     }
13256   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13257       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13258       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13259     commentList[index] = (char *) malloc(len + 2);
13260     strncpy(commentList[index], text, len);
13261     commentList[index][len] = '\n';
13262     commentList[index][len + 1] = NULLCHAR;
13263   } else {
13264     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13265     char *p;
13266     commentList[index] = (char *) malloc(len + 7);
13267     safeStrCpy(commentList[index], "{\n", 3);
13268     safeStrCpy(commentList[index]+2, text, len+1);
13269     commentList[index][len+2] = NULLCHAR;
13270     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13271     strcat(commentList[index], "\n}\n");
13272   }
13273 }
13274
13275 void
13276 CrushCRs(text)
13277      char *text;
13278 {
13279   char *p = text;
13280   char *q = text;
13281   char ch;
13282
13283   do {
13284     ch = *p++;
13285     if (ch == '\r') continue;
13286     *q++ = ch;
13287   } while (ch != '\0');
13288 }
13289
13290 void
13291 AppendComment(index, text, addBraces)
13292      int index;
13293      char *text;
13294      Boolean addBraces; // [HGM] braces: tells if we should add {}
13295 {
13296     int oldlen, len;
13297     char *old;
13298
13299 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13300     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13301
13302     CrushCRs(text);
13303     while (*text == '\n') text++;
13304     len = strlen(text);
13305     while (len > 0 && text[len - 1] == '\n') len--;
13306
13307     if (len == 0) return;
13308
13309     if (commentList[index] != NULL) {
13310         old = commentList[index];
13311         oldlen = strlen(old);
13312         while(commentList[index][oldlen-1] ==  '\n')
13313           commentList[index][--oldlen] = NULLCHAR;
13314         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13315         safeStrCpy(commentList[index], old, oldlen);
13316         free(old);
13317         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13318         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13319           if(addBraces) addBraces = FALSE; else { text++; len--; }
13320           while (*text == '\n') { text++; len--; }
13321           commentList[index][--oldlen] = NULLCHAR;
13322       }
13323         if(addBraces) strcat(commentList[index], "\n{\n");
13324         else          strcat(commentList[index], "\n");
13325         strcat(commentList[index], text);
13326         if(addBraces) strcat(commentList[index], "\n}\n");
13327         else          strcat(commentList[index], "\n");
13328     } else {
13329         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13330         if(addBraces)
13331           safeStrCpy(commentList[index], "{\n", 3);
13332         else commentList[index][0] = NULLCHAR;
13333         strcat(commentList[index], text);
13334         strcat(commentList[index], "\n");
13335         if(addBraces) strcat(commentList[index], "}\n");
13336     }
13337 }
13338
13339 static char * FindStr( char * text, char * sub_text )
13340 {
13341     char * result = strstr( text, sub_text );
13342
13343     if( result != NULL ) {
13344         result += strlen( sub_text );
13345     }
13346
13347     return result;
13348 }
13349
13350 /* [AS] Try to extract PV info from PGN comment */
13351 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13352 char *GetInfoFromComment( int index, char * text )
13353 {
13354     char * sep = text;
13355
13356     if( text != NULL && index > 0 ) {
13357         int score = 0;
13358         int depth = 0;
13359         int time = -1, sec = 0, deci;
13360         char * s_eval = FindStr( text, "[%eval " );
13361         char * s_emt = FindStr( text, "[%emt " );
13362
13363         if( s_eval != NULL || s_emt != NULL ) {
13364             /* New style */
13365             char delim;
13366
13367             if( s_eval != NULL ) {
13368                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13369                     return text;
13370                 }
13371
13372                 if( delim != ']' ) {
13373                     return text;
13374                 }
13375             }
13376
13377             if( s_emt != NULL ) {
13378             }
13379                 return text;
13380         }
13381         else {
13382             /* We expect something like: [+|-]nnn.nn/dd */
13383             int score_lo = 0;
13384
13385             if(*text != '{') return text; // [HGM] braces: must be normal comment
13386
13387             sep = strchr( text, '/' );
13388             if( sep == NULL || sep < (text+4) ) {
13389                 return text;
13390             }
13391
13392             time = -1; sec = -1; deci = -1;
13393             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13394                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13395                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13396                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13397                 return text;
13398             }
13399
13400             if( score_lo < 0 || score_lo >= 100 ) {
13401                 return text;
13402             }
13403
13404             if(sec >= 0) time = 600*time + 10*sec; else
13405             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13406
13407             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13408
13409             /* [HGM] PV time: now locate end of PV info */
13410             while( *++sep >= '0' && *sep <= '9'); // strip depth
13411             if(time >= 0)
13412             while( *++sep >= '0' && *sep <= '9'); // strip time
13413             if(sec >= 0)
13414             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13415             if(deci >= 0)
13416             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13417             while(*sep == ' ') sep++;
13418         }
13419
13420         if( depth <= 0 ) {
13421             return text;
13422         }
13423
13424         if( time < 0 ) {
13425             time = -1;
13426         }
13427
13428         pvInfoList[index-1].depth = depth;
13429         pvInfoList[index-1].score = score;
13430         pvInfoList[index-1].time  = 10*time; // centi-sec
13431         if(*sep == '}') *sep = 0; else *--sep = '{';
13432     }
13433     return sep;
13434 }
13435
13436 void
13437 SendToProgram(message, cps)
13438      char *message;
13439      ChessProgramState *cps;
13440 {
13441     int count, outCount, error;
13442     char buf[MSG_SIZ];
13443
13444     if (cps->pr == NULL) return;
13445     Attention(cps);
13446
13447     if (appData.debugMode) {
13448         TimeMark now;
13449         GetTimeMark(&now);
13450         fprintf(debugFP, "%ld >%-6s: %s",
13451                 SubtractTimeMarks(&now, &programStartTime),
13452                 cps->which, message);
13453     }
13454
13455     count = strlen(message);
13456     outCount = OutputToProcess(cps->pr, message, count, &error);
13457     if (outCount < count && !exiting
13458                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13459       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13460         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13461             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13462                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13463                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13464             } else {
13465                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13466             }
13467             gameInfo.resultDetails = StrSave(buf);
13468         }
13469         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13470     }
13471 }
13472
13473 void
13474 ReceiveFromProgram(isr, closure, message, count, error)
13475      InputSourceRef isr;
13476      VOIDSTAR closure;
13477      char *message;
13478      int count;
13479      int error;
13480 {
13481     char *end_str;
13482     char buf[MSG_SIZ];
13483     ChessProgramState *cps = (ChessProgramState *)closure;
13484
13485     if (isr != cps->isr) return; /* Killed intentionally */
13486     if (count <= 0) {
13487         if (count == 0) {
13488             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13489                     cps->which, cps->program);
13490         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13491                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13492                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13493                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13494                 } else {
13495                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13496                 }
13497                 gameInfo.resultDetails = StrSave(buf);
13498             }
13499             RemoveInputSource(cps->isr);
13500             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13501         } else {
13502             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13503                     cps->which, cps->program);
13504             RemoveInputSource(cps->isr);
13505
13506             /* [AS] Program is misbehaving badly... kill it */
13507             if( count == -2 ) {
13508                 DestroyChildProcess( cps->pr, 9 );
13509                 cps->pr = NoProc;
13510             }
13511
13512             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13513         }
13514         return;
13515     }
13516
13517     if ((end_str = strchr(message, '\r')) != NULL)
13518       *end_str = NULLCHAR;
13519     if ((end_str = strchr(message, '\n')) != NULL)
13520       *end_str = NULLCHAR;
13521
13522     if (appData.debugMode) {
13523         TimeMark now; int print = 1;
13524         char *quote = ""; char c; int i;
13525
13526         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13527                 char start = message[0];
13528                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13529                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13530                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13531                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13532                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13533                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13534                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13535                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13536                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13537                     print = (appData.engineComments >= 2);
13538                 }
13539                 message[0] = start; // restore original message
13540         }
13541         if(print) {
13542                 GetTimeMark(&now);
13543                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13544                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13545                         quote,
13546                         message);
13547         }
13548     }
13549
13550     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13551     if (appData.icsEngineAnalyze) {
13552         if (strstr(message, "whisper") != NULL ||
13553              strstr(message, "kibitz") != NULL ||
13554             strstr(message, "tellics") != NULL) return;
13555     }
13556
13557     HandleMachineMove(message, cps);
13558 }
13559
13560
13561 void
13562 SendTimeControl(cps, mps, tc, inc, sd, st)
13563      ChessProgramState *cps;
13564      int mps, inc, sd, st;
13565      long tc;
13566 {
13567     char buf[MSG_SIZ];
13568     int seconds;
13569
13570     if( timeControl_2 > 0 ) {
13571         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13572             tc = timeControl_2;
13573         }
13574     }
13575     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13576     inc /= cps->timeOdds;
13577     st  /= cps->timeOdds;
13578
13579     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13580
13581     if (st > 0) {
13582       /* Set exact time per move, normally using st command */
13583       if (cps->stKludge) {
13584         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13585         seconds = st % 60;
13586         if (seconds == 0) {
13587           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13588         } else {
13589           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13590         }
13591       } else {
13592         snprintf(buf, MSG_SIZ, "st %d\n", st);
13593       }
13594     } else {
13595       /* Set conventional or incremental time control, using level command */
13596       if (seconds == 0) {
13597         /* Note old gnuchess bug -- minutes:seconds used to not work.
13598            Fixed in later versions, but still avoid :seconds
13599            when seconds is 0. */
13600         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13601       } else {
13602         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13603                  seconds, inc/1000.);
13604       }
13605     }
13606     SendToProgram(buf, cps);
13607
13608     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13609     /* Orthogonally, limit search to given depth */
13610     if (sd > 0) {
13611       if (cps->sdKludge) {
13612         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13613       } else {
13614         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13615       }
13616       SendToProgram(buf, cps);
13617     }
13618
13619     if(cps->nps > 0) { /* [HGM] nps */
13620         if(cps->supportsNPS == FALSE)
13621           cps->nps = -1; // don't use if engine explicitly says not supported!
13622         else {
13623           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13624           SendToProgram(buf, cps);
13625         }
13626     }
13627 }
13628
13629 ChessProgramState *WhitePlayer()
13630 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13631 {
13632     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13633        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13634         return &second;
13635     return &first;
13636 }
13637
13638 void
13639 SendTimeRemaining(cps, machineWhite)
13640      ChessProgramState *cps;
13641      int /*boolean*/ machineWhite;
13642 {
13643     char message[MSG_SIZ];
13644     long time, otime;
13645
13646     /* Note: this routine must be called when the clocks are stopped
13647        or when they have *just* been set or switched; otherwise
13648        it will be off by the time since the current tick started.
13649     */
13650     if (machineWhite) {
13651         time = whiteTimeRemaining / 10;
13652         otime = blackTimeRemaining / 10;
13653     } else {
13654         time = blackTimeRemaining / 10;
13655         otime = whiteTimeRemaining / 10;
13656     }
13657     /* [HGM] translate opponent's time by time-odds factor */
13658     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13659     if (appData.debugMode) {
13660         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13661     }
13662
13663     if (time <= 0) time = 1;
13664     if (otime <= 0) otime = 1;
13665
13666     snprintf(message, MSG_SIZ, "time %ld\n", time);
13667     SendToProgram(message, cps);
13668
13669     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13670     SendToProgram(message, cps);
13671 }
13672
13673 int
13674 BoolFeature(p, name, loc, cps)
13675      char **p;
13676      char *name;
13677      int *loc;
13678      ChessProgramState *cps;
13679 {
13680   char buf[MSG_SIZ];
13681   int len = strlen(name);
13682   int val;
13683
13684   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13685     (*p) += len + 1;
13686     sscanf(*p, "%d", &val);
13687     *loc = (val != 0);
13688     while (**p && **p != ' ')
13689       (*p)++;
13690     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13691     SendToProgram(buf, cps);
13692     return TRUE;
13693   }
13694   return FALSE;
13695 }
13696
13697 int
13698 IntFeature(p, name, loc, cps)
13699      char **p;
13700      char *name;
13701      int *loc;
13702      ChessProgramState *cps;
13703 {
13704   char buf[MSG_SIZ];
13705   int len = strlen(name);
13706   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13707     (*p) += len + 1;
13708     sscanf(*p, "%d", loc);
13709     while (**p && **p != ' ') (*p)++;
13710     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13711     SendToProgram(buf, cps);
13712     return TRUE;
13713   }
13714   return FALSE;
13715 }
13716
13717 int
13718 StringFeature(p, name, loc, cps)
13719      char **p;
13720      char *name;
13721      char loc[];
13722      ChessProgramState *cps;
13723 {
13724   char buf[MSG_SIZ];
13725   int len = strlen(name);
13726   if (strncmp((*p), name, len) == 0
13727       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13728     (*p) += len + 2;
13729     sscanf(*p, "%[^\"]", loc);
13730     while (**p && **p != '\"') (*p)++;
13731     if (**p == '\"') (*p)++;
13732     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13733     SendToProgram(buf, cps);
13734     return TRUE;
13735   }
13736   return FALSE;
13737 }
13738
13739 int
13740 ParseOption(Option *opt, ChessProgramState *cps)
13741 // [HGM] options: process the string that defines an engine option, and determine
13742 // name, type, default value, and allowed value range
13743 {
13744         char *p, *q, buf[MSG_SIZ];
13745         int n, min = (-1)<<31, max = 1<<31, def;
13746
13747         if(p = strstr(opt->name, " -spin ")) {
13748             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13749             if(max < min) max = min; // enforce consistency
13750             if(def < min) def = min;
13751             if(def > max) def = max;
13752             opt->value = def;
13753             opt->min = min;
13754             opt->max = max;
13755             opt->type = Spin;
13756         } else if((p = strstr(opt->name, " -slider "))) {
13757             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13758             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13759             if(max < min) max = min; // enforce consistency
13760             if(def < min) def = min;
13761             if(def > max) def = max;
13762             opt->value = def;
13763             opt->min = min;
13764             opt->max = max;
13765             opt->type = Spin; // Slider;
13766         } else if((p = strstr(opt->name, " -string "))) {
13767             opt->textValue = p+9;
13768             opt->type = TextBox;
13769         } else if((p = strstr(opt->name, " -file "))) {
13770             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13771             opt->textValue = p+7;
13772             opt->type = TextBox; // FileName;
13773         } else if((p = strstr(opt->name, " -path "))) {
13774             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13775             opt->textValue = p+7;
13776             opt->type = TextBox; // PathName;
13777         } else if(p = strstr(opt->name, " -check ")) {
13778             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13779             opt->value = (def != 0);
13780             opt->type = CheckBox;
13781         } else if(p = strstr(opt->name, " -combo ")) {
13782             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13783             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13784             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13785             opt->value = n = 0;
13786             while(q = StrStr(q, " /// ")) {
13787                 n++; *q = 0;    // count choices, and null-terminate each of them
13788                 q += 5;
13789                 if(*q == '*') { // remember default, which is marked with * prefix
13790                     q++;
13791                     opt->value = n;
13792                 }
13793                 cps->comboList[cps->comboCnt++] = q;
13794             }
13795             cps->comboList[cps->comboCnt++] = NULL;
13796             opt->max = n + 1;
13797             opt->type = ComboBox;
13798         } else if(p = strstr(opt->name, " -button")) {
13799             opt->type = Button;
13800         } else if(p = strstr(opt->name, " -save")) {
13801             opt->type = SaveButton;
13802         } else return FALSE;
13803         *p = 0; // terminate option name
13804         // now look if the command-line options define a setting for this engine option.
13805         if(cps->optionSettings && cps->optionSettings[0])
13806             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13807         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13808           snprintf(buf, MSG_SIZ, "option %s", p);
13809                 if(p = strstr(buf, ",")) *p = 0;
13810                 strcat(buf, "\n");
13811                 SendToProgram(buf, cps);
13812         }
13813         return TRUE;
13814 }
13815
13816 void
13817 FeatureDone(cps, val)
13818      ChessProgramState* cps;
13819      int val;
13820 {
13821   DelayedEventCallback cb = GetDelayedEvent();
13822   if ((cb == InitBackEnd3 && cps == &first) ||
13823       (cb == TwoMachinesEventIfReady && cps == &second)) {
13824     CancelDelayedEvent();
13825     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13826   }
13827   cps->initDone = val;
13828 }
13829
13830 /* Parse feature command from engine */
13831 void
13832 ParseFeatures(args, cps)
13833      char* args;
13834      ChessProgramState *cps;
13835 {
13836   char *p = args;
13837   char *q;
13838   int val;
13839   char buf[MSG_SIZ];
13840
13841   for (;;) {
13842     while (*p == ' ') p++;
13843     if (*p == NULLCHAR) return;
13844
13845     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13846     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13847     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13848     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13849     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13850     if (BoolFeature(&p, "reuse", &val, cps)) {
13851       /* Engine can disable reuse, but can't enable it if user said no */
13852       if (!val) cps->reuse = FALSE;
13853       continue;
13854     }
13855     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13856     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13857       if (gameMode == TwoMachinesPlay) {
13858         DisplayTwoMachinesTitle();
13859       } else {
13860         DisplayTitle("");
13861       }
13862       continue;
13863     }
13864     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13865     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13866     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13867     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13868     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13869     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13870     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13871     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13872     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13873     if (IntFeature(&p, "done", &val, cps)) {
13874       FeatureDone(cps, val);
13875       continue;
13876     }
13877     /* Added by Tord: */
13878     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13879     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13880     /* End of additions by Tord */
13881
13882     /* [HGM] added features: */
13883     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13884     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13885     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13886     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13887     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13888     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13889     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13890         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13891           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13892             SendToProgram(buf, cps);
13893             continue;
13894         }
13895         if(cps->nrOptions >= MAX_OPTIONS) {
13896             cps->nrOptions--;
13897             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13898             DisplayError(buf, 0);
13899         }
13900         continue;
13901     }
13902     /* End of additions by HGM */
13903
13904     /* unknown feature: complain and skip */
13905     q = p;
13906     while (*q && *q != '=') q++;
13907     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13908     SendToProgram(buf, cps);
13909     p = q;
13910     if (*p == '=') {
13911       p++;
13912       if (*p == '\"') {
13913         p++;
13914         while (*p && *p != '\"') p++;
13915         if (*p == '\"') p++;
13916       } else {
13917         while (*p && *p != ' ') p++;
13918       }
13919     }
13920   }
13921
13922 }
13923
13924 void
13925 PeriodicUpdatesEvent(newState)
13926      int newState;
13927 {
13928     if (newState == appData.periodicUpdates)
13929       return;
13930
13931     appData.periodicUpdates=newState;
13932
13933     /* Display type changes, so update it now */
13934 //    DisplayAnalysis();
13935
13936     /* Get the ball rolling again... */
13937     if (newState) {
13938         AnalysisPeriodicEvent(1);
13939         StartAnalysisClock();
13940     }
13941 }
13942
13943 void
13944 PonderNextMoveEvent(newState)
13945      int newState;
13946 {
13947     if (newState == appData.ponderNextMove) return;
13948     if (gameMode == EditPosition) EditPositionDone(TRUE);
13949     if (newState) {
13950         SendToProgram("hard\n", &first);
13951         if (gameMode == TwoMachinesPlay) {
13952             SendToProgram("hard\n", &second);
13953         }
13954     } else {
13955         SendToProgram("easy\n", &first);
13956         thinkOutput[0] = NULLCHAR;
13957         if (gameMode == TwoMachinesPlay) {
13958             SendToProgram("easy\n", &second);
13959         }
13960     }
13961     appData.ponderNextMove = newState;
13962 }
13963
13964 void
13965 NewSettingEvent(option, feature, command, value)
13966      char *command;
13967      int option, value, *feature;
13968 {
13969     char buf[MSG_SIZ];
13970
13971     if (gameMode == EditPosition) EditPositionDone(TRUE);
13972     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
13973     if(feature == NULL || *feature) SendToProgram(buf, &first);
13974     if (gameMode == TwoMachinesPlay) {
13975         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13976     }
13977 }
13978
13979 void
13980 ShowThinkingEvent()
13981 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13982 {
13983     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13984     int newState = appData.showThinking
13985         // [HGM] thinking: other features now need thinking output as well
13986         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13987
13988     if (oldState == newState) return;
13989     oldState = newState;
13990     if (gameMode == EditPosition) EditPositionDone(TRUE);
13991     if (oldState) {
13992         SendToProgram("post\n", &first);
13993         if (gameMode == TwoMachinesPlay) {
13994             SendToProgram("post\n", &second);
13995         }
13996     } else {
13997         SendToProgram("nopost\n", &first);
13998         thinkOutput[0] = NULLCHAR;
13999         if (gameMode == TwoMachinesPlay) {
14000             SendToProgram("nopost\n", &second);
14001         }
14002     }
14003 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14004 }
14005
14006 void
14007 AskQuestionEvent(title, question, replyPrefix, which)
14008      char *title; char *question; char *replyPrefix; char *which;
14009 {
14010   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14011   if (pr == NoProc) return;
14012   AskQuestion(title, question, replyPrefix, pr);
14013 }
14014
14015 void
14016 DisplayMove(moveNumber)
14017      int moveNumber;
14018 {
14019     char message[MSG_SIZ];
14020     char res[MSG_SIZ];
14021     char cpThinkOutput[MSG_SIZ];
14022
14023     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14024
14025     if (moveNumber == forwardMostMove - 1 ||
14026         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14027
14028         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14029
14030         if (strchr(cpThinkOutput, '\n')) {
14031             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14032         }
14033     } else {
14034         *cpThinkOutput = NULLCHAR;
14035     }
14036
14037     /* [AS] Hide thinking from human user */
14038     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14039         *cpThinkOutput = NULLCHAR;
14040         if( thinkOutput[0] != NULLCHAR ) {
14041             int i;
14042
14043             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14044                 cpThinkOutput[i] = '.';
14045             }
14046             cpThinkOutput[i] = NULLCHAR;
14047             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14048         }
14049     }
14050
14051     if (moveNumber == forwardMostMove - 1 &&
14052         gameInfo.resultDetails != NULL) {
14053         if (gameInfo.resultDetails[0] == NULLCHAR) {
14054           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14055         } else {
14056           snprintf(res, MSG_SIZ, " {%s} %s",
14057                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14058         }
14059     } else {
14060         res[0] = NULLCHAR;
14061     }
14062
14063     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14064         DisplayMessage(res, cpThinkOutput);
14065     } else {
14066       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14067                 WhiteOnMove(moveNumber) ? " " : ".. ",
14068                 parseList[moveNumber], res);
14069         DisplayMessage(message, cpThinkOutput);
14070     }
14071 }
14072
14073 void
14074 DisplayComment(moveNumber, text)
14075      int moveNumber;
14076      char *text;
14077 {
14078     char title[MSG_SIZ];
14079     char buf[8000]; // comment can be long!
14080     int score, depth;
14081
14082     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14083       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14084     } else {
14085       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14086               WhiteOnMove(moveNumber) ? " " : ".. ",
14087               parseList[moveNumber]);
14088     }
14089     // [HGM] PV info: display PV info together with (or as) comment
14090     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14091       if(text == NULL) text = "";
14092       score = pvInfoList[moveNumber].score;
14093       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14094               depth, (pvInfoList[moveNumber].time+50)/100, text);
14095       text = buf;
14096     }
14097     if (text != NULL && (appData.autoDisplayComment || commentUp))
14098         CommentPopUp(title, text);
14099 }
14100
14101 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14102  * might be busy thinking or pondering.  It can be omitted if your
14103  * gnuchess is configured to stop thinking immediately on any user
14104  * input.  However, that gnuchess feature depends on the FIONREAD
14105  * ioctl, which does not work properly on some flavors of Unix.
14106  */
14107 void
14108 Attention(cps)
14109      ChessProgramState *cps;
14110 {
14111 #if ATTENTION
14112     if (!cps->useSigint) return;
14113     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14114     switch (gameMode) {
14115       case MachinePlaysWhite:
14116       case MachinePlaysBlack:
14117       case TwoMachinesPlay:
14118       case IcsPlayingWhite:
14119       case IcsPlayingBlack:
14120       case AnalyzeMode:
14121       case AnalyzeFile:
14122         /* Skip if we know it isn't thinking */
14123         if (!cps->maybeThinking) return;
14124         if (appData.debugMode)
14125           fprintf(debugFP, "Interrupting %s\n", cps->which);
14126         InterruptChildProcess(cps->pr);
14127         cps->maybeThinking = FALSE;
14128         break;
14129       default:
14130         break;
14131     }
14132 #endif /*ATTENTION*/
14133 }
14134
14135 int
14136 CheckFlags()
14137 {
14138     if (whiteTimeRemaining <= 0) {
14139         if (!whiteFlag) {
14140             whiteFlag = TRUE;
14141             if (appData.icsActive) {
14142                 if (appData.autoCallFlag &&
14143                     gameMode == IcsPlayingBlack && !blackFlag) {
14144                   SendToICS(ics_prefix);
14145                   SendToICS("flag\n");
14146                 }
14147             } else {
14148                 if (blackFlag) {
14149                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14150                 } else {
14151                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14152                     if (appData.autoCallFlag) {
14153                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14154                         return TRUE;
14155                     }
14156                 }
14157             }
14158         }
14159     }
14160     if (blackTimeRemaining <= 0) {
14161         if (!blackFlag) {
14162             blackFlag = TRUE;
14163             if (appData.icsActive) {
14164                 if (appData.autoCallFlag &&
14165                     gameMode == IcsPlayingWhite && !whiteFlag) {
14166                   SendToICS(ics_prefix);
14167                   SendToICS("flag\n");
14168                 }
14169             } else {
14170                 if (whiteFlag) {
14171                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14172                 } else {
14173                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14174                     if (appData.autoCallFlag) {
14175                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14176                         return TRUE;
14177                     }
14178                 }
14179             }
14180         }
14181     }
14182     return FALSE;
14183 }
14184
14185 void
14186 CheckTimeControl()
14187 {
14188     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14189         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14190
14191     /*
14192      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14193      */
14194     if ( !WhiteOnMove(forwardMostMove) ) {
14195         /* White made time control */
14196         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14197         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14198         /* [HGM] time odds: correct new time quota for time odds! */
14199                                             / WhitePlayer()->timeOdds;
14200         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14201     } else {
14202         lastBlack -= blackTimeRemaining;
14203         /* Black made time control */
14204         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14205                                             / WhitePlayer()->other->timeOdds;
14206         lastWhite = whiteTimeRemaining;
14207     }
14208 }
14209
14210 void
14211 DisplayBothClocks()
14212 {
14213     int wom = gameMode == EditPosition ?
14214       !blackPlaysFirst : WhiteOnMove(currentMove);
14215     DisplayWhiteClock(whiteTimeRemaining, wom);
14216     DisplayBlackClock(blackTimeRemaining, !wom);
14217 }
14218
14219
14220 /* Timekeeping seems to be a portability nightmare.  I think everyone
14221    has ftime(), but I'm really not sure, so I'm including some ifdefs
14222    to use other calls if you don't.  Clocks will be less accurate if
14223    you have neither ftime nor gettimeofday.
14224 */
14225
14226 /* VS 2008 requires the #include outside of the function */
14227 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14228 #include <sys/timeb.h>
14229 #endif
14230
14231 /* Get the current time as a TimeMark */
14232 void
14233 GetTimeMark(tm)
14234      TimeMark *tm;
14235 {
14236 #if HAVE_GETTIMEOFDAY
14237
14238     struct timeval timeVal;
14239     struct timezone timeZone;
14240
14241     gettimeofday(&timeVal, &timeZone);
14242     tm->sec = (long) timeVal.tv_sec;
14243     tm->ms = (int) (timeVal.tv_usec / 1000L);
14244
14245 #else /*!HAVE_GETTIMEOFDAY*/
14246 #if HAVE_FTIME
14247
14248 // include <sys/timeb.h> / moved to just above start of function
14249     struct timeb timeB;
14250
14251     ftime(&timeB);
14252     tm->sec = (long) timeB.time;
14253     tm->ms = (int) timeB.millitm;
14254
14255 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14256     tm->sec = (long) time(NULL);
14257     tm->ms = 0;
14258 #endif
14259 #endif
14260 }
14261
14262 /* Return the difference in milliseconds between two
14263    time marks.  We assume the difference will fit in a long!
14264 */
14265 long
14266 SubtractTimeMarks(tm2, tm1)
14267      TimeMark *tm2, *tm1;
14268 {
14269     return 1000L*(tm2->sec - tm1->sec) +
14270            (long) (tm2->ms - tm1->ms);
14271 }
14272
14273
14274 /*
14275  * Code to manage the game clocks.
14276  *
14277  * In tournament play, black starts the clock and then white makes a move.
14278  * We give the human user a slight advantage if he is playing white---the
14279  * clocks don't run until he makes his first move, so it takes zero time.
14280  * Also, we don't account for network lag, so we could get out of sync
14281  * with GNU Chess's clock -- but then, referees are always right.
14282  */
14283
14284 static TimeMark tickStartTM;
14285 static long intendedTickLength;
14286
14287 long
14288 NextTickLength(timeRemaining)
14289      long timeRemaining;
14290 {
14291     long nominalTickLength, nextTickLength;
14292
14293     if (timeRemaining > 0L && timeRemaining <= 10000L)
14294       nominalTickLength = 100L;
14295     else
14296       nominalTickLength = 1000L;
14297     nextTickLength = timeRemaining % nominalTickLength;
14298     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14299
14300     return nextTickLength;
14301 }
14302
14303 /* Adjust clock one minute up or down */
14304 void
14305 AdjustClock(Boolean which, int dir)
14306 {
14307     if(which) blackTimeRemaining += 60000*dir;
14308     else      whiteTimeRemaining += 60000*dir;
14309     DisplayBothClocks();
14310 }
14311
14312 /* Stop clocks and reset to a fresh time control */
14313 void
14314 ResetClocks()
14315 {
14316     (void) StopClockTimer();
14317     if (appData.icsActive) {
14318         whiteTimeRemaining = blackTimeRemaining = 0;
14319     } else if (searchTime) {
14320         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14321         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14322     } else { /* [HGM] correct new time quote for time odds */
14323         whiteTC = blackTC = fullTimeControlString;
14324         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14325         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14326     }
14327     if (whiteFlag || blackFlag) {
14328         DisplayTitle("");
14329         whiteFlag = blackFlag = FALSE;
14330     }
14331     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14332     DisplayBothClocks();
14333 }
14334
14335 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14336
14337 /* Decrement running clock by amount of time that has passed */
14338 void
14339 DecrementClocks()
14340 {
14341     long timeRemaining;
14342     long lastTickLength, fudge;
14343     TimeMark now;
14344
14345     if (!appData.clockMode) return;
14346     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14347
14348     GetTimeMark(&now);
14349
14350     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14351
14352     /* Fudge if we woke up a little too soon */
14353     fudge = intendedTickLength - lastTickLength;
14354     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14355
14356     if (WhiteOnMove(forwardMostMove)) {
14357         if(whiteNPS >= 0) lastTickLength = 0;
14358         timeRemaining = whiteTimeRemaining -= lastTickLength;
14359         if(timeRemaining < 0 && !appData.icsActive) {
14360             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14361             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14362                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14363                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14364             }
14365         }
14366         DisplayWhiteClock(whiteTimeRemaining - fudge,
14367                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14368     } else {
14369         if(blackNPS >= 0) lastTickLength = 0;
14370         timeRemaining = blackTimeRemaining -= lastTickLength;
14371         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14372             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14373             if(suddenDeath) {
14374                 blackStartMove = forwardMostMove;
14375                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14376             }
14377         }
14378         DisplayBlackClock(blackTimeRemaining - fudge,
14379                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14380     }
14381     if (CheckFlags()) return;
14382
14383     tickStartTM = now;
14384     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14385     StartClockTimer(intendedTickLength);
14386
14387     /* if the time remaining has fallen below the alarm threshold, sound the
14388      * alarm. if the alarm has sounded and (due to a takeback or time control
14389      * with increment) the time remaining has increased to a level above the
14390      * threshold, reset the alarm so it can sound again.
14391      */
14392
14393     if (appData.icsActive && appData.icsAlarm) {
14394
14395         /* make sure we are dealing with the user's clock */
14396         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14397                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14398            )) return;
14399
14400         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14401             alarmSounded = FALSE;
14402         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14403             PlayAlarmSound();
14404             alarmSounded = TRUE;
14405         }
14406     }
14407 }
14408
14409
14410 /* A player has just moved, so stop the previously running
14411    clock and (if in clock mode) start the other one.
14412    We redisplay both clocks in case we're in ICS mode, because
14413    ICS gives us an update to both clocks after every move.
14414    Note that this routine is called *after* forwardMostMove
14415    is updated, so the last fractional tick must be subtracted
14416    from the color that is *not* on move now.
14417 */
14418 void
14419 SwitchClocks(int newMoveNr)
14420 {
14421     long lastTickLength;
14422     TimeMark now;
14423     int flagged = FALSE;
14424
14425     GetTimeMark(&now);
14426
14427     if (StopClockTimer() && appData.clockMode) {
14428         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14429         if (!WhiteOnMove(forwardMostMove)) {
14430             if(blackNPS >= 0) lastTickLength = 0;
14431             blackTimeRemaining -= lastTickLength;
14432            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14433 //         if(pvInfoList[forwardMostMove-1].time == -1)
14434                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14435                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14436         } else {
14437            if(whiteNPS >= 0) lastTickLength = 0;
14438            whiteTimeRemaining -= lastTickLength;
14439            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14440 //         if(pvInfoList[forwardMostMove-1].time == -1)
14441                  pvInfoList[forwardMostMove-1].time =
14442                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14443         }
14444         flagged = CheckFlags();
14445     }
14446     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14447     CheckTimeControl();
14448
14449     if (flagged || !appData.clockMode) return;
14450
14451     switch (gameMode) {
14452       case MachinePlaysBlack:
14453       case MachinePlaysWhite:
14454       case BeginningOfGame:
14455         if (pausing) return;
14456         break;
14457
14458       case EditGame:
14459       case PlayFromGameFile:
14460       case IcsExamining:
14461         return;
14462
14463       default:
14464         break;
14465     }
14466
14467     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14468         if(WhiteOnMove(forwardMostMove))
14469              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14470         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14471     }
14472
14473     tickStartTM = now;
14474     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14475       whiteTimeRemaining : blackTimeRemaining);
14476     StartClockTimer(intendedTickLength);
14477 }
14478
14479
14480 /* Stop both clocks */
14481 void
14482 StopClocks()
14483 {
14484     long lastTickLength;
14485     TimeMark now;
14486
14487     if (!StopClockTimer()) return;
14488     if (!appData.clockMode) return;
14489
14490     GetTimeMark(&now);
14491
14492     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14493     if (WhiteOnMove(forwardMostMove)) {
14494         if(whiteNPS >= 0) lastTickLength = 0;
14495         whiteTimeRemaining -= lastTickLength;
14496         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14497     } else {
14498         if(blackNPS >= 0) lastTickLength = 0;
14499         blackTimeRemaining -= lastTickLength;
14500         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14501     }
14502     CheckFlags();
14503 }
14504
14505 /* Start clock of player on move.  Time may have been reset, so
14506    if clock is already running, stop and restart it. */
14507 void
14508 StartClocks()
14509 {
14510     (void) StopClockTimer(); /* in case it was running already */
14511     DisplayBothClocks();
14512     if (CheckFlags()) return;
14513
14514     if (!appData.clockMode) return;
14515     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14516
14517     GetTimeMark(&tickStartTM);
14518     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14519       whiteTimeRemaining : blackTimeRemaining);
14520
14521    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14522     whiteNPS = blackNPS = -1;
14523     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14524        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14525         whiteNPS = first.nps;
14526     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14527        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14528         blackNPS = first.nps;
14529     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14530         whiteNPS = second.nps;
14531     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14532         blackNPS = second.nps;
14533     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14534
14535     StartClockTimer(intendedTickLength);
14536 }
14537
14538 char *
14539 TimeString(ms)
14540      long ms;
14541 {
14542     long second, minute, hour, day;
14543     char *sign = "";
14544     static char buf[32];
14545
14546     if (ms > 0 && ms <= 9900) {
14547       /* convert milliseconds to tenths, rounding up */
14548       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14549
14550       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14551       return buf;
14552     }
14553
14554     /* convert milliseconds to seconds, rounding up */
14555     /* use floating point to avoid strangeness of integer division
14556        with negative dividends on many machines */
14557     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14558
14559     if (second < 0) {
14560         sign = "-";
14561         second = -second;
14562     }
14563
14564     day = second / (60 * 60 * 24);
14565     second = second % (60 * 60 * 24);
14566     hour = second / (60 * 60);
14567     second = second % (60 * 60);
14568     minute = second / 60;
14569     second = second % 60;
14570
14571     if (day > 0)
14572       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14573               sign, day, hour, minute, second);
14574     else if (hour > 0)
14575       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14576     else
14577       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14578
14579     return buf;
14580 }
14581
14582
14583 /*
14584  * This is necessary because some C libraries aren't ANSI C compliant yet.
14585  */
14586 char *
14587 StrStr(string, match)
14588      char *string, *match;
14589 {
14590     int i, length;
14591
14592     length = strlen(match);
14593
14594     for (i = strlen(string) - length; i >= 0; i--, string++)
14595       if (!strncmp(match, string, length))
14596         return string;
14597
14598     return NULL;
14599 }
14600
14601 char *
14602 StrCaseStr(string, match)
14603      char *string, *match;
14604 {
14605     int i, j, length;
14606
14607     length = strlen(match);
14608
14609     for (i = strlen(string) - length; i >= 0; i--, string++) {
14610         for (j = 0; j < length; j++) {
14611             if (ToLower(match[j]) != ToLower(string[j]))
14612               break;
14613         }
14614         if (j == length) return string;
14615     }
14616
14617     return NULL;
14618 }
14619
14620 #ifndef _amigados
14621 int
14622 StrCaseCmp(s1, s2)
14623      char *s1, *s2;
14624 {
14625     char c1, c2;
14626
14627     for (;;) {
14628         c1 = ToLower(*s1++);
14629         c2 = ToLower(*s2++);
14630         if (c1 > c2) return 1;
14631         if (c1 < c2) return -1;
14632         if (c1 == NULLCHAR) return 0;
14633     }
14634 }
14635
14636
14637 int
14638 ToLower(c)
14639      int c;
14640 {
14641     return isupper(c) ? tolower(c) : c;
14642 }
14643
14644
14645 int
14646 ToUpper(c)
14647      int c;
14648 {
14649     return islower(c) ? toupper(c) : c;
14650 }
14651 #endif /* !_amigados    */
14652
14653 char *
14654 StrSave(s)
14655      char *s;
14656 {
14657   char *ret;
14658
14659   if ((ret = (char *) malloc(strlen(s) + 1)))
14660     {
14661       safeStrCpy(ret, s, strlen(s)+1);
14662     }
14663   return ret;
14664 }
14665
14666 char *
14667 StrSavePtr(s, savePtr)
14668      char *s, **savePtr;
14669 {
14670     if (*savePtr) {
14671         free(*savePtr);
14672     }
14673     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14674       safeStrCpy(*savePtr, s, strlen(s)+1);
14675     }
14676     return(*savePtr);
14677 }
14678
14679 char *
14680 PGNDate()
14681 {
14682     time_t clock;
14683     struct tm *tm;
14684     char buf[MSG_SIZ];
14685
14686     clock = time((time_t *)NULL);
14687     tm = localtime(&clock);
14688     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14689             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14690     return StrSave(buf);
14691 }
14692
14693
14694 char *
14695 PositionToFEN(move, overrideCastling)
14696      int move;
14697      char *overrideCastling;
14698 {
14699     int i, j, fromX, fromY, toX, toY;
14700     int whiteToPlay;
14701     char buf[128];
14702     char *p, *q;
14703     int emptycount;
14704     ChessSquare piece;
14705
14706     whiteToPlay = (gameMode == EditPosition) ?
14707       !blackPlaysFirst : (move % 2 == 0);
14708     p = buf;
14709
14710     /* Piece placement data */
14711     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14712         emptycount = 0;
14713         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14714             if (boards[move][i][j] == EmptySquare) {
14715                 emptycount++;
14716             } else { ChessSquare piece = boards[move][i][j];
14717                 if (emptycount > 0) {
14718                     if(emptycount<10) /* [HGM] can be >= 10 */
14719                         *p++ = '0' + emptycount;
14720                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14721                     emptycount = 0;
14722                 }
14723                 if(PieceToChar(piece) == '+') {
14724                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14725                     *p++ = '+';
14726                     piece = (ChessSquare)(DEMOTED piece);
14727                 }
14728                 *p++ = PieceToChar(piece);
14729                 if(p[-1] == '~') {
14730                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14731                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14732                     *p++ = '~';
14733                 }
14734             }
14735         }
14736         if (emptycount > 0) {
14737             if(emptycount<10) /* [HGM] can be >= 10 */
14738                 *p++ = '0' + emptycount;
14739             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14740             emptycount = 0;
14741         }
14742         *p++ = '/';
14743     }
14744     *(p - 1) = ' ';
14745
14746     /* [HGM] print Crazyhouse or Shogi holdings */
14747     if( gameInfo.holdingsWidth ) {
14748         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14749         q = p;
14750         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14751             piece = boards[move][i][BOARD_WIDTH-1];
14752             if( piece != EmptySquare )
14753               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14754                   *p++ = PieceToChar(piece);
14755         }
14756         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14757             piece = boards[move][BOARD_HEIGHT-i-1][0];
14758             if( piece != EmptySquare )
14759               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14760                   *p++ = PieceToChar(piece);
14761         }
14762
14763         if( q == p ) *p++ = '-';
14764         *p++ = ']';
14765         *p++ = ' ';
14766     }
14767
14768     /* Active color */
14769     *p++ = whiteToPlay ? 'w' : 'b';
14770     *p++ = ' ';
14771
14772   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14773     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14774   } else {
14775   if(nrCastlingRights) {
14776      q = p;
14777      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14778        /* [HGM] write directly from rights */
14779            if(boards[move][CASTLING][2] != NoRights &&
14780               boards[move][CASTLING][0] != NoRights   )
14781                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14782            if(boards[move][CASTLING][2] != NoRights &&
14783               boards[move][CASTLING][1] != NoRights   )
14784                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14785            if(boards[move][CASTLING][5] != NoRights &&
14786               boards[move][CASTLING][3] != NoRights   )
14787                 *p++ = boards[move][CASTLING][3] + AAA;
14788            if(boards[move][CASTLING][5] != NoRights &&
14789               boards[move][CASTLING][4] != NoRights   )
14790                 *p++ = boards[move][CASTLING][4] + AAA;
14791      } else {
14792
14793         /* [HGM] write true castling rights */
14794         if( nrCastlingRights == 6 ) {
14795             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14796                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14797             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14798                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14799             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14800                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14801             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14802                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14803         }
14804      }
14805      if (q == p) *p++ = '-'; /* No castling rights */
14806      *p++ = ' ';
14807   }
14808
14809   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14810      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14811     /* En passant target square */
14812     if (move > backwardMostMove) {
14813         fromX = moveList[move - 1][0] - AAA;
14814         fromY = moveList[move - 1][1] - ONE;
14815         toX = moveList[move - 1][2] - AAA;
14816         toY = moveList[move - 1][3] - ONE;
14817         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14818             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14819             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14820             fromX == toX) {
14821             /* 2-square pawn move just happened */
14822             *p++ = toX + AAA;
14823             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14824         } else {
14825             *p++ = '-';
14826         }
14827     } else if(move == backwardMostMove) {
14828         // [HGM] perhaps we should always do it like this, and forget the above?
14829         if((signed char)boards[move][EP_STATUS] >= 0) {
14830             *p++ = boards[move][EP_STATUS] + AAA;
14831             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14832         } else {
14833             *p++ = '-';
14834         }
14835     } else {
14836         *p++ = '-';
14837     }
14838     *p++ = ' ';
14839   }
14840   }
14841
14842     /* [HGM] find reversible plies */
14843     {   int i = 0, j=move;
14844
14845         if (appData.debugMode) { int k;
14846             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14847             for(k=backwardMostMove; k<=forwardMostMove; k++)
14848                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14849
14850         }
14851
14852         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14853         if( j == backwardMostMove ) i += initialRulePlies;
14854         sprintf(p, "%d ", i);
14855         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14856     }
14857     /* Fullmove number */
14858     sprintf(p, "%d", (move / 2) + 1);
14859
14860     return StrSave(buf);
14861 }
14862
14863 Boolean
14864 ParseFEN(board, blackPlaysFirst, fen)
14865     Board board;
14866      int *blackPlaysFirst;
14867      char *fen;
14868 {
14869     int i, j;
14870     char *p, c;
14871     int emptycount;
14872     ChessSquare piece;
14873
14874     p = fen;
14875
14876     /* [HGM] by default clear Crazyhouse holdings, if present */
14877     if(gameInfo.holdingsWidth) {
14878        for(i=0; i<BOARD_HEIGHT; i++) {
14879            board[i][0]             = EmptySquare; /* black holdings */
14880            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14881            board[i][1]             = (ChessSquare) 0; /* black counts */
14882            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14883        }
14884     }
14885
14886     /* Piece placement data */
14887     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14888         j = 0;
14889         for (;;) {
14890             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14891                 if (*p == '/') p++;
14892                 emptycount = gameInfo.boardWidth - j;
14893                 while (emptycount--)
14894                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14895                 break;
14896 #if(BOARD_FILES >= 10)
14897             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14898                 p++; emptycount=10;
14899                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14900                 while (emptycount--)
14901                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14902 #endif
14903             } else if (isdigit(*p)) {
14904                 emptycount = *p++ - '0';
14905                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14906                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14907                 while (emptycount--)
14908                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14909             } else if (*p == '+' || isalpha(*p)) {
14910                 if (j >= gameInfo.boardWidth) return FALSE;
14911                 if(*p=='+') {
14912                     piece = CharToPiece(*++p);
14913                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14914                     piece = (ChessSquare) (PROMOTED piece ); p++;
14915                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14916                 } else piece = CharToPiece(*p++);
14917
14918                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14919                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14920                     piece = (ChessSquare) (PROMOTED piece);
14921                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14922                     p++;
14923                 }
14924                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14925             } else {
14926                 return FALSE;
14927             }
14928         }
14929     }
14930     while (*p == '/' || *p == ' ') p++;
14931
14932     /* [HGM] look for Crazyhouse holdings here */
14933     while(*p==' ') p++;
14934     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14935         if(*p == '[') p++;
14936         if(*p == '-' ) p++; /* empty holdings */ else {
14937             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14938             /* if we would allow FEN reading to set board size, we would   */
14939             /* have to add holdings and shift the board read so far here   */
14940             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14941                 p++;
14942                 if((int) piece >= (int) BlackPawn ) {
14943                     i = (int)piece - (int)BlackPawn;
14944                     i = PieceToNumber((ChessSquare)i);
14945                     if( i >= gameInfo.holdingsSize ) return FALSE;
14946                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14947                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14948                 } else {
14949                     i = (int)piece - (int)WhitePawn;
14950                     i = PieceToNumber((ChessSquare)i);
14951                     if( i >= gameInfo.holdingsSize ) return FALSE;
14952                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14953                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14954                 }
14955             }
14956         }
14957         if(*p == ']') p++;
14958     }
14959
14960     while(*p == ' ') p++;
14961
14962     /* Active color */
14963     c = *p++;
14964     if(appData.colorNickNames) {
14965       if( c == appData.colorNickNames[0] ) c = 'w'; else
14966       if( c == appData.colorNickNames[1] ) c = 'b';
14967     }
14968     switch (c) {
14969       case 'w':
14970         *blackPlaysFirst = FALSE;
14971         break;
14972       case 'b':
14973         *blackPlaysFirst = TRUE;
14974         break;
14975       default:
14976         return FALSE;
14977     }
14978
14979     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14980     /* return the extra info in global variiables             */
14981
14982     /* set defaults in case FEN is incomplete */
14983     board[EP_STATUS] = EP_UNKNOWN;
14984     for(i=0; i<nrCastlingRights; i++ ) {
14985         board[CASTLING][i] =
14986             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14987     }   /* assume possible unless obviously impossible */
14988     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14989     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14990     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14991                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14992     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14993     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14994     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14995                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14996     FENrulePlies = 0;
14997
14998     while(*p==' ') p++;
14999     if(nrCastlingRights) {
15000       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15001           /* castling indicator present, so default becomes no castlings */
15002           for(i=0; i<nrCastlingRights; i++ ) {
15003                  board[CASTLING][i] = NoRights;
15004           }
15005       }
15006       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15007              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15008              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15009              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15010         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15011
15012         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15013             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15014             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15015         }
15016         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15017             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15018         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15019                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15020         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15021                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15022         switch(c) {
15023           case'K':
15024               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15025               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15026               board[CASTLING][2] = whiteKingFile;
15027               break;
15028           case'Q':
15029               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15030               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15031               board[CASTLING][2] = whiteKingFile;
15032               break;
15033           case'k':
15034               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15035               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15036               board[CASTLING][5] = blackKingFile;
15037               break;
15038           case'q':
15039               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15040               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15041               board[CASTLING][5] = blackKingFile;
15042           case '-':
15043               break;
15044           default: /* FRC castlings */
15045               if(c >= 'a') { /* black rights */
15046                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15047                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15048                   if(i == BOARD_RGHT) break;
15049                   board[CASTLING][5] = i;
15050                   c -= AAA;
15051                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15052                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15053                   if(c > i)
15054                       board[CASTLING][3] = c;
15055                   else
15056                       board[CASTLING][4] = c;
15057               } else { /* white rights */
15058                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15059                     if(board[0][i] == WhiteKing) break;
15060                   if(i == BOARD_RGHT) break;
15061                   board[CASTLING][2] = i;
15062                   c -= AAA - 'a' + 'A';
15063                   if(board[0][c] >= WhiteKing) break;
15064                   if(c > i)
15065                       board[CASTLING][0] = c;
15066                   else
15067                       board[CASTLING][1] = c;
15068               }
15069         }
15070       }
15071       for(i=0; i<nrCastlingRights; i++)
15072         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15073     if (appData.debugMode) {
15074         fprintf(debugFP, "FEN castling rights:");
15075         for(i=0; i<nrCastlingRights; i++)
15076         fprintf(debugFP, " %d", board[CASTLING][i]);
15077         fprintf(debugFP, "\n");
15078     }
15079
15080       while(*p==' ') p++;
15081     }
15082
15083     /* read e.p. field in games that know e.p. capture */
15084     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15085        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15086       if(*p=='-') {
15087         p++; board[EP_STATUS] = EP_NONE;
15088       } else {
15089          char c = *p++ - AAA;
15090
15091          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15092          if(*p >= '0' && *p <='9') p++;
15093          board[EP_STATUS] = c;
15094       }
15095     }
15096
15097
15098     if(sscanf(p, "%d", &i) == 1) {
15099         FENrulePlies = i; /* 50-move ply counter */
15100         /* (The move number is still ignored)    */
15101     }
15102
15103     return TRUE;
15104 }
15105
15106 void
15107 EditPositionPasteFEN(char *fen)
15108 {
15109   if (fen != NULL) {
15110     Board initial_position;
15111
15112     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15113       DisplayError(_("Bad FEN position in clipboard"), 0);
15114       return ;
15115     } else {
15116       int savedBlackPlaysFirst = blackPlaysFirst;
15117       EditPositionEvent();
15118       blackPlaysFirst = savedBlackPlaysFirst;
15119       CopyBoard(boards[0], initial_position);
15120       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15121       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15122       DisplayBothClocks();
15123       DrawPosition(FALSE, boards[currentMove]);
15124     }
15125   }
15126 }
15127
15128 static char cseq[12] = "\\   ";
15129
15130 Boolean set_cont_sequence(char *new_seq)
15131 {
15132     int len;
15133     Boolean ret;
15134
15135     // handle bad attempts to set the sequence
15136         if (!new_seq)
15137                 return 0; // acceptable error - no debug
15138
15139     len = strlen(new_seq);
15140     ret = (len > 0) && (len < sizeof(cseq));
15141     if (ret)
15142       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15143     else if (appData.debugMode)
15144       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15145     return ret;
15146 }
15147
15148 /*
15149     reformat a source message so words don't cross the width boundary.  internal
15150     newlines are not removed.  returns the wrapped size (no null character unless
15151     included in source message).  If dest is NULL, only calculate the size required
15152     for the dest buffer.  lp argument indicats line position upon entry, and it's
15153     passed back upon exit.
15154 */
15155 int wrap(char *dest, char *src, int count, int width, int *lp)
15156 {
15157     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15158
15159     cseq_len = strlen(cseq);
15160     old_line = line = *lp;
15161     ansi = len = clen = 0;
15162
15163     for (i=0; i < count; i++)
15164     {
15165         if (src[i] == '\033')
15166             ansi = 1;
15167
15168         // if we hit the width, back up
15169         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15170         {
15171             // store i & len in case the word is too long
15172             old_i = i, old_len = len;
15173
15174             // find the end of the last word
15175             while (i && src[i] != ' ' && src[i] != '\n')
15176             {
15177                 i--;
15178                 len--;
15179             }
15180
15181             // word too long?  restore i & len before splitting it
15182             if ((old_i-i+clen) >= width)
15183             {
15184                 i = old_i;
15185                 len = old_len;
15186             }
15187
15188             // extra space?
15189             if (i && src[i-1] == ' ')
15190                 len--;
15191
15192             if (src[i] != ' ' && src[i] != '\n')
15193             {
15194                 i--;
15195                 if (len)
15196                     len--;
15197             }
15198
15199             // now append the newline and continuation sequence
15200             if (dest)
15201                 dest[len] = '\n';
15202             len++;
15203             if (dest)
15204                 strncpy(dest+len, cseq, cseq_len);
15205             len += cseq_len;
15206             line = cseq_len;
15207             clen = cseq_len;
15208             continue;
15209         }
15210
15211         if (dest)
15212             dest[len] = src[i];
15213         len++;
15214         if (!ansi)
15215             line++;
15216         if (src[i] == '\n')
15217             line = 0;
15218         if (src[i] == 'm')
15219             ansi = 0;
15220     }
15221     if (dest && appData.debugMode)
15222     {
15223         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15224             count, width, line, len, *lp);
15225         show_bytes(debugFP, src, count);
15226         fprintf(debugFP, "\ndest: ");
15227         show_bytes(debugFP, dest, len);
15228         fprintf(debugFP, "\n");
15229     }
15230     *lp = dest ? line : old_line;
15231
15232     return len;
15233 }
15234
15235 // [HGM] vari: routines for shelving variations
15236
15237 void
15238 PushTail(int firstMove, int lastMove)
15239 {
15240         int i, j, nrMoves = lastMove - firstMove;
15241
15242         if(appData.icsActive) { // only in local mode
15243                 forwardMostMove = currentMove; // mimic old ICS behavior
15244                 return;
15245         }
15246         if(storedGames >= MAX_VARIATIONS-1) return;
15247
15248         // push current tail of game on stack
15249         savedResult[storedGames] = gameInfo.result;
15250         savedDetails[storedGames] = gameInfo.resultDetails;
15251         gameInfo.resultDetails = NULL;
15252         savedFirst[storedGames] = firstMove;
15253         savedLast [storedGames] = lastMove;
15254         savedFramePtr[storedGames] = framePtr;
15255         framePtr -= nrMoves; // reserve space for the boards
15256         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15257             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15258             for(j=0; j<MOVE_LEN; j++)
15259                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15260             for(j=0; j<2*MOVE_LEN; j++)
15261                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15262             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15263             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15264             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15265             pvInfoList[firstMove+i-1].depth = 0;
15266             commentList[framePtr+i] = commentList[firstMove+i];
15267             commentList[firstMove+i] = NULL;
15268         }
15269
15270         storedGames++;
15271         forwardMostMove = firstMove; // truncate game so we can start variation
15272         if(storedGames == 1) GreyRevert(FALSE);
15273 }
15274
15275 Boolean
15276 PopTail(Boolean annotate)
15277 {
15278         int i, j, nrMoves;
15279         char buf[8000], moveBuf[20];
15280
15281         if(appData.icsActive) return FALSE; // only in local mode
15282         if(!storedGames) return FALSE; // sanity
15283         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15284
15285         storedGames--;
15286         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15287         nrMoves = savedLast[storedGames] - currentMove;
15288         if(annotate) {
15289                 int cnt = 10;
15290                 if(!WhiteOnMove(currentMove))
15291                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15292                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15293                 for(i=currentMove; i<forwardMostMove; i++) {
15294                         if(WhiteOnMove(i))
15295                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15296                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15297                         strcat(buf, moveBuf);
15298                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15299                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15300                 }
15301                 strcat(buf, ")");
15302         }
15303         for(i=1; i<=nrMoves; i++) { // copy last variation back
15304             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15305             for(j=0; j<MOVE_LEN; j++)
15306                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15307             for(j=0; j<2*MOVE_LEN; j++)
15308                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15309             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15310             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15311             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15312             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15313             commentList[currentMove+i] = commentList[framePtr+i];
15314             commentList[framePtr+i] = NULL;
15315         }
15316         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15317         framePtr = savedFramePtr[storedGames];
15318         gameInfo.result = savedResult[storedGames];
15319         if(gameInfo.resultDetails != NULL) {
15320             free(gameInfo.resultDetails);
15321       }
15322         gameInfo.resultDetails = savedDetails[storedGames];
15323         forwardMostMove = currentMove + nrMoves;
15324         if(storedGames == 0) GreyRevert(TRUE);
15325         return TRUE;
15326 }
15327
15328 void
15329 CleanupTail()
15330 {       // remove all shelved variations
15331         int i;
15332         for(i=0; i<storedGames; i++) {
15333             if(savedDetails[i])
15334                 free(savedDetails[i]);
15335             savedDetails[i] = NULL;
15336         }
15337         for(i=framePtr; i<MAX_MOVES; i++) {
15338                 if(commentList[i]) free(commentList[i]);
15339                 commentList[i] = NULL;
15340         }
15341         framePtr = MAX_MOVES-1;
15342         storedGames = 0;
15343 }
15344
15345 void
15346 LoadVariation(int index, char *text)
15347 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15348         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15349         int level = 0, move;
15350
15351         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15352         // first find outermost bracketing variation
15353         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15354             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15355                 if(*p == '{') wait = '}'; else
15356                 if(*p == '[') wait = ']'; else
15357                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15358                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15359             }
15360             if(*p == wait) wait = NULLCHAR; // closing ]} found
15361             p++;
15362         }
15363         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15364         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15365         end[1] = NULLCHAR; // clip off comment beyond variation
15366         ToNrEvent(currentMove-1);
15367         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15368         // kludge: use ParsePV() to append variation to game
15369         move = currentMove;
15370         ParsePV(start, TRUE);
15371         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15372         ClearPremoveHighlights();
15373         CommentPopDown();
15374         ToNrEvent(currentMove+1);
15375 }
15376