16fdc62ae564ea11c28a4a71f2461421e1e90577
[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     if (appData.debugMode) {
4888         fprintf(debugFP, "move to parse: %s\n", move);
4889     }
4890     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4891
4892     switch (*moveType) {
4893       case WhitePromotion:
4894       case BlackPromotion:
4895       case WhiteNonPromotion:
4896       case BlackNonPromotion:
4897       case NormalMove:
4898       case WhiteCapturesEnPassant:
4899       case BlackCapturesEnPassant:
4900       case WhiteKingSideCastle:
4901       case WhiteQueenSideCastle:
4902       case BlackKingSideCastle:
4903       case BlackQueenSideCastle:
4904       case WhiteKingSideCastleWild:
4905       case WhiteQueenSideCastleWild:
4906       case BlackKingSideCastleWild:
4907       case BlackQueenSideCastleWild:
4908       /* Code added by Tord: */
4909       case WhiteHSideCastleFR:
4910       case WhiteASideCastleFR:
4911       case BlackHSideCastleFR:
4912       case BlackASideCastleFR:
4913       /* End of code added by Tord */
4914       case IllegalMove:         /* bug or odd chess variant */
4915         *fromX = currentMoveString[0] - AAA;
4916         *fromY = currentMoveString[1] - ONE;
4917         *toX = currentMoveString[2] - AAA;
4918         *toY = currentMoveString[3] - ONE;
4919         *promoChar = currentMoveString[4];
4920         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4921             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4922     if (appData.debugMode) {
4923         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4924     }
4925             *fromX = *fromY = *toX = *toY = 0;
4926             return FALSE;
4927         }
4928         if (appData.testLegality) {
4929           return (*moveType != IllegalMove);
4930         } else {
4931           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4932                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4933         }
4934
4935       case WhiteDrop:
4936       case BlackDrop:
4937         *fromX = *moveType == WhiteDrop ?
4938           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4939           (int) CharToPiece(ToLower(currentMoveString[0]));
4940         *fromY = DROP_RANK;
4941         *toX = currentMoveString[2] - AAA;
4942         *toY = currentMoveString[3] - ONE;
4943         *promoChar = NULLCHAR;
4944         return TRUE;
4945
4946       case AmbiguousMove:
4947       case ImpossibleMove:
4948       case EndOfFile:
4949       case ElapsedTime:
4950       case Comment:
4951       case PGNTag:
4952       case NAG:
4953       case WhiteWins:
4954       case BlackWins:
4955       case GameIsDrawn:
4956       default:
4957     if (appData.debugMode) {
4958         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4959     }
4960         /* bug? */
4961         *fromX = *fromY = *toX = *toY = 0;
4962         *promoChar = NULLCHAR;
4963         return FALSE;
4964     }
4965 }
4966
4967
4968 void
4969 ParsePV(char *pv, Boolean storeComments)
4970 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4971   int fromX, fromY, toX, toY; char promoChar;
4972   ChessMove moveType;
4973   Boolean valid;
4974   int nr = 0;
4975
4976   endPV = forwardMostMove;
4977   do {
4978     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4979     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4980     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4981 if(appData.debugMode){
4982 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);
4983 }
4984     if(!valid && nr == 0 &&
4985        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4986         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4987         // Hande case where played move is different from leading PV move
4988         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4989         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4990         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4991         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4992           endPV += 2; // if position different, keep this
4993           moveList[endPV-1][0] = fromX + AAA;
4994           moveList[endPV-1][1] = fromY + ONE;
4995           moveList[endPV-1][2] = toX + AAA;
4996           moveList[endPV-1][3] = toY + ONE;
4997           parseList[endPV-1][0] = NULLCHAR;
4998           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4999         }
5000       }
5001     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5002     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5003     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5004     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5005         valid++; // allow comments in PV
5006         continue;
5007     }
5008     nr++;
5009     if(endPV+1 > framePtr) break; // no space, truncate
5010     if(!valid) break;
5011     endPV++;
5012     CopyBoard(boards[endPV], boards[endPV-1]);
5013     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5014     moveList[endPV-1][0] = fromX + AAA;
5015     moveList[endPV-1][1] = fromY + ONE;
5016     moveList[endPV-1][2] = toX + AAA;
5017     moveList[endPV-1][3] = toY + ONE;
5018     if(storeComments)
5019         CoordsToAlgebraic(boards[endPV - 1],
5020                              PosFlags(endPV - 1),
5021                              fromY, fromX, toY, toX, promoChar,
5022                              parseList[endPV - 1]);
5023     else
5024         parseList[endPV-1][0] = NULLCHAR;
5025   } while(valid);
5026   currentMove = endPV;
5027   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5028   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5029                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5030   DrawPosition(TRUE, boards[currentMove]);
5031 }
5032
5033 static int lastX, lastY;
5034
5035 Boolean
5036 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5037 {
5038         int startPV;
5039         char *p;
5040
5041         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5042         lastX = x; lastY = y;
5043         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5044         startPV = index;
5045         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5046         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5047         index = startPV;
5048         do{ while(buf[index] && buf[index] != '\n') index++;
5049         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5050         buf[index] = 0;
5051         ParsePV(buf+startPV, FALSE);
5052         *start = startPV; *end = index-1;
5053         return TRUE;
5054 }
5055
5056 Boolean
5057 LoadPV(int x, int y)
5058 { // called on right mouse click to load PV
5059   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5060   lastX = x; lastY = y;
5061   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5062   return TRUE;
5063 }
5064
5065 void
5066 UnLoadPV()
5067 {
5068   if(endPV < 0) return;
5069   endPV = -1;
5070   currentMove = forwardMostMove;
5071   ClearPremoveHighlights();
5072   DrawPosition(TRUE, boards[currentMove]);
5073 }
5074
5075 void
5076 MovePV(int x, int y, int h)
5077 { // step through PV based on mouse coordinates (called on mouse move)
5078   int margin = h>>3, step = 0;
5079
5080   if(endPV < 0) return;
5081   // we must somehow check if right button is still down (might be released off board!)
5082   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5083   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5084   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5085   if(!step) return;
5086   lastX = x; lastY = y;
5087   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5088   currentMove += step;
5089   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5090   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5091                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5092   DrawPosition(FALSE, boards[currentMove]);
5093 }
5094
5095
5096 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5097 // All positions will have equal probability, but the current method will not provide a unique
5098 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5099 #define DARK 1
5100 #define LITE 2
5101 #define ANY 3
5102
5103 int squaresLeft[4];
5104 int piecesLeft[(int)BlackPawn];
5105 int seed, nrOfShuffles;
5106
5107 void GetPositionNumber()
5108 {       // sets global variable seed
5109         int i;
5110
5111         seed = appData.defaultFrcPosition;
5112         if(seed < 0) { // randomize based on time for negative FRC position numbers
5113                 for(i=0; i<50; i++) seed += random();
5114                 seed = random() ^ random() >> 8 ^ random() << 8;
5115                 if(seed<0) seed = -seed;
5116         }
5117 }
5118
5119 int put(Board board, int pieceType, int rank, int n, int shade)
5120 // put the piece on the (n-1)-th empty squares of the given shade
5121 {
5122         int i;
5123
5124         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5125                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5126                         board[rank][i] = (ChessSquare) pieceType;
5127                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5128                         squaresLeft[ANY]--;
5129                         piecesLeft[pieceType]--;
5130                         return i;
5131                 }
5132         }
5133         return -1;
5134 }
5135
5136
5137 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5138 // calculate where the next piece goes, (any empty square), and put it there
5139 {
5140         int i;
5141
5142         i = seed % squaresLeft[shade];
5143         nrOfShuffles *= squaresLeft[shade];
5144         seed /= squaresLeft[shade];
5145         put(board, pieceType, rank, i, shade);
5146 }
5147
5148 void AddTwoPieces(Board board, int pieceType, int rank)
5149 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5150 {
5151         int i, n=squaresLeft[ANY], j=n-1, k;
5152
5153         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5154         i = seed % k;  // pick one
5155         nrOfShuffles *= k;
5156         seed /= k;
5157         while(i >= j) i -= j--;
5158         j = n - 1 - j; i += j;
5159         put(board, pieceType, rank, j, ANY);
5160         put(board, pieceType, rank, i, ANY);
5161 }
5162
5163 void SetUpShuffle(Board board, int number)
5164 {
5165         int i, p, first=1;
5166
5167         GetPositionNumber(); nrOfShuffles = 1;
5168
5169         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5170         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5171         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5172
5173         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5174
5175         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5176             p = (int) board[0][i];
5177             if(p < (int) BlackPawn) piecesLeft[p] ++;
5178             board[0][i] = EmptySquare;
5179         }
5180
5181         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5182             // shuffles restricted to allow normal castling put KRR first
5183             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5184                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5185             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5186                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5187             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5188                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5189             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5190                 put(board, WhiteRook, 0, 0, ANY);
5191             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5192         }
5193
5194         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5195             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5196             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5197                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5198                 while(piecesLeft[p] >= 2) {
5199                     AddOnePiece(board, p, 0, LITE);
5200                     AddOnePiece(board, p, 0, DARK);
5201                 }
5202                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5203             }
5204
5205         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5206             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5207             // but we leave King and Rooks for last, to possibly obey FRC restriction
5208             if(p == (int)WhiteRook) continue;
5209             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5210             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5211         }
5212
5213         // now everything is placed, except perhaps King (Unicorn) and Rooks
5214
5215         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5216             // Last King gets castling rights
5217             while(piecesLeft[(int)WhiteUnicorn]) {
5218                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5219                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5220             }
5221
5222             while(piecesLeft[(int)WhiteKing]) {
5223                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5224                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5225             }
5226
5227
5228         } else {
5229             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5230             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5231         }
5232
5233         // Only Rooks can be left; simply place them all
5234         while(piecesLeft[(int)WhiteRook]) {
5235                 i = put(board, WhiteRook, 0, 0, ANY);
5236                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5237                         if(first) {
5238                                 first=0;
5239                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5240                         }
5241                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5242                 }
5243         }
5244         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5245             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5246         }
5247
5248         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5249 }
5250
5251 int SetCharTable( char *table, const char * map )
5252 /* [HGM] moved here from winboard.c because of its general usefulness */
5253 /*       Basically a safe strcpy that uses the last character as King */
5254 {
5255     int result = FALSE; int NrPieces;
5256
5257     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5258                     && NrPieces >= 12 && !(NrPieces&1)) {
5259         int i; /* [HGM] Accept even length from 12 to 34 */
5260
5261         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5262         for( i=0; i<NrPieces/2-1; i++ ) {
5263             table[i] = map[i];
5264             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5265         }
5266         table[(int) WhiteKing]  = map[NrPieces/2-1];
5267         table[(int) BlackKing]  = map[NrPieces-1];
5268
5269         result = TRUE;
5270     }
5271
5272     return result;
5273 }
5274
5275 void Prelude(Board board)
5276 {       // [HGM] superchess: random selection of exo-pieces
5277         int i, j, k; ChessSquare p;
5278         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5279
5280         GetPositionNumber(); // use FRC position number
5281
5282         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5283             SetCharTable(pieceToChar, appData.pieceToCharTable);
5284             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5285                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5286         }
5287
5288         j = seed%4;                 seed /= 4;
5289         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5290         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5291         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5292         j = seed%3 + (seed%3 >= j); seed /= 3;
5293         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5294         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5295         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5296         j = seed%3;                 seed /= 3;
5297         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5298         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5299         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5300         j = seed%2 + (seed%2 >= j); seed /= 2;
5301         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5302         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5303         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5304         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5305         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5306         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5307         put(board, exoPieces[0],    0, 0, ANY);
5308         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5309 }
5310
5311 void
5312 InitPosition(redraw)
5313      int redraw;
5314 {
5315     ChessSquare (* pieces)[BOARD_FILES];
5316     int i, j, pawnRow, overrule,
5317     oldx = gameInfo.boardWidth,
5318     oldy = gameInfo.boardHeight,
5319     oldh = gameInfo.holdingsWidth,
5320     oldv = gameInfo.variant;
5321
5322     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5323
5324     /* [AS] Initialize pv info list [HGM] and game status */
5325     {
5326         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5327             pvInfoList[i].depth = 0;
5328             boards[i][EP_STATUS] = EP_NONE;
5329             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5330         }
5331
5332         initialRulePlies = 0; /* 50-move counter start */
5333
5334         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5335         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5336     }
5337
5338
5339     /* [HGM] logic here is completely changed. In stead of full positions */
5340     /* the initialized data only consist of the two backranks. The switch */
5341     /* selects which one we will use, which is than copied to the Board   */
5342     /* initialPosition, which for the rest is initialized by Pawns and    */
5343     /* empty squares. This initial position is then copied to boards[0],  */
5344     /* possibly after shuffling, so that it remains available.            */
5345
5346     gameInfo.holdingsWidth = 0; /* default board sizes */
5347     gameInfo.boardWidth    = 8;
5348     gameInfo.boardHeight   = 8;
5349     gameInfo.holdingsSize  = 0;
5350     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5351     for(i=0; i<BOARD_FILES-2; i++)
5352       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5353     initialPosition[EP_STATUS] = EP_NONE;
5354     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5355     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5356          SetCharTable(pieceNickName, appData.pieceNickNames);
5357     else SetCharTable(pieceNickName, "............");
5358
5359     switch (gameInfo.variant) {
5360     case VariantFischeRandom:
5361       shuffleOpenings = TRUE;
5362     default:
5363       pieces = FIDEArray;
5364       break;
5365     case VariantShatranj:
5366       pieces = ShatranjArray;
5367       nrCastlingRights = 0;
5368       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5369       break;
5370     case VariantMakruk:
5371       pieces = makrukArray;
5372       nrCastlingRights = 0;
5373       startedFromSetupPosition = TRUE;
5374       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5375       break;
5376     case VariantTwoKings:
5377       pieces = twoKingsArray;
5378       break;
5379     case VariantCapaRandom:
5380       shuffleOpenings = TRUE;
5381     case VariantCapablanca:
5382       pieces = CapablancaArray;
5383       gameInfo.boardWidth = 10;
5384       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5385       break;
5386     case VariantGothic:
5387       pieces = GothicArray;
5388       gameInfo.boardWidth = 10;
5389       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5390       break;
5391     case VariantJanus:
5392       pieces = JanusArray;
5393       gameInfo.boardWidth = 10;
5394       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5395       nrCastlingRights = 6;
5396         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5397         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5398         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5399         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5400         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5401         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5402       break;
5403     case VariantFalcon:
5404       pieces = FalconArray;
5405       gameInfo.boardWidth = 10;
5406       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5407       break;
5408     case VariantXiangqi:
5409       pieces = XiangqiArray;
5410       gameInfo.boardWidth  = 9;
5411       gameInfo.boardHeight = 10;
5412       nrCastlingRights = 0;
5413       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5414       break;
5415     case VariantShogi:
5416       pieces = ShogiArray;
5417       gameInfo.boardWidth  = 9;
5418       gameInfo.boardHeight = 9;
5419       gameInfo.holdingsSize = 7;
5420       nrCastlingRights = 0;
5421       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5422       break;
5423     case VariantCourier:
5424       pieces = CourierArray;
5425       gameInfo.boardWidth  = 12;
5426       nrCastlingRights = 0;
5427       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5428       break;
5429     case VariantKnightmate:
5430       pieces = KnightmateArray;
5431       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5432       break;
5433     case VariantFairy:
5434       pieces = fairyArray;
5435       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5436       break;
5437     case VariantGreat:
5438       pieces = GreatArray;
5439       gameInfo.boardWidth = 10;
5440       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5441       gameInfo.holdingsSize = 8;
5442       break;
5443     case VariantSuper:
5444       pieces = FIDEArray;
5445       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5446       gameInfo.holdingsSize = 8;
5447       startedFromSetupPosition = TRUE;
5448       break;
5449     case VariantCrazyhouse:
5450     case VariantBughouse:
5451       pieces = FIDEArray;
5452       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5453       gameInfo.holdingsSize = 5;
5454       break;
5455     case VariantWildCastle:
5456       pieces = FIDEArray;
5457       /* !!?shuffle with kings guaranteed to be on d or e file */
5458       shuffleOpenings = 1;
5459       break;
5460     case VariantNoCastle:
5461       pieces = FIDEArray;
5462       nrCastlingRights = 0;
5463       /* !!?unconstrained back-rank shuffle */
5464       shuffleOpenings = 1;
5465       break;
5466     }
5467
5468     overrule = 0;
5469     if(appData.NrFiles >= 0) {
5470         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5471         gameInfo.boardWidth = appData.NrFiles;
5472     }
5473     if(appData.NrRanks >= 0) {
5474         gameInfo.boardHeight = appData.NrRanks;
5475     }
5476     if(appData.holdingsSize >= 0) {
5477         i = appData.holdingsSize;
5478         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5479         gameInfo.holdingsSize = i;
5480     }
5481     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5482     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5483         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5484
5485     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5486     if(pawnRow < 1) pawnRow = 1;
5487     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5488
5489     /* User pieceToChar list overrules defaults */
5490     if(appData.pieceToCharTable != NULL)
5491         SetCharTable(pieceToChar, appData.pieceToCharTable);
5492
5493     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5494
5495         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5496             s = (ChessSquare) 0; /* account holding counts in guard band */
5497         for( i=0; i<BOARD_HEIGHT; i++ )
5498             initialPosition[i][j] = s;
5499
5500         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5501         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5502         initialPosition[pawnRow][j] = WhitePawn;
5503         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5504         if(gameInfo.variant == VariantXiangqi) {
5505             if(j&1) {
5506                 initialPosition[pawnRow][j] =
5507                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5508                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5509                    initialPosition[2][j] = WhiteCannon;
5510                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5511                 }
5512             }
5513         }
5514         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5515     }
5516     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5517
5518             j=BOARD_LEFT+1;
5519             initialPosition[1][j] = WhiteBishop;
5520             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5521             j=BOARD_RGHT-2;
5522             initialPosition[1][j] = WhiteRook;
5523             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5524     }
5525
5526     if( nrCastlingRights == -1) {
5527         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5528         /*       This sets default castling rights from none to normal corners   */
5529         /* Variants with other castling rights must set them themselves above    */
5530         nrCastlingRights = 6;
5531
5532         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5533         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5534         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5535         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5536         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5537         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5538      }
5539
5540      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5541      if(gameInfo.variant == VariantGreat) { // promotion commoners
5542         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5543         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5544         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5545         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5546      }
5547   if (appData.debugMode) {
5548     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5549   }
5550     if(shuffleOpenings) {
5551         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5552         startedFromSetupPosition = TRUE;
5553     }
5554     if(startedFromPositionFile) {
5555       /* [HGM] loadPos: use PositionFile for every new game */
5556       CopyBoard(initialPosition, filePosition);
5557       for(i=0; i<nrCastlingRights; i++)
5558           initialRights[i] = filePosition[CASTLING][i];
5559       startedFromSetupPosition = TRUE;
5560     }
5561
5562     CopyBoard(boards[0], initialPosition);
5563
5564     if(oldx != gameInfo.boardWidth ||
5565        oldy != gameInfo.boardHeight ||
5566        oldh != gameInfo.holdingsWidth
5567 #ifdef GOTHIC
5568        || oldv == VariantGothic ||        // For licensing popups
5569        gameInfo.variant == VariantGothic
5570 #endif
5571 #ifdef FALCON
5572        || oldv == VariantFalcon ||
5573        gameInfo.variant == VariantFalcon
5574 #endif
5575                                          )
5576             InitDrawingSizes(-2 ,0);
5577
5578     if (redraw)
5579       DrawPosition(TRUE, boards[currentMove]);
5580 }
5581
5582 void
5583 SendBoard(cps, moveNum)
5584      ChessProgramState *cps;
5585      int moveNum;
5586 {
5587     char message[MSG_SIZ];
5588
5589     if (cps->useSetboard) {
5590       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5591       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5592       SendToProgram(message, cps);
5593       free(fen);
5594
5595     } else {
5596       ChessSquare *bp;
5597       int i, j;
5598       /* Kludge to set black to move, avoiding the troublesome and now
5599        * deprecated "black" command.
5600        */
5601       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5602
5603       SendToProgram("edit\n", cps);
5604       SendToProgram("#\n", cps);
5605       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5606         bp = &boards[moveNum][i][BOARD_LEFT];
5607         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5608           if ((int) *bp < (int) BlackPawn) {
5609             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5610                     AAA + j, ONE + i);
5611             if(message[0] == '+' || message[0] == '~') {
5612               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5613                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5614                         AAA + j, ONE + i);
5615             }
5616             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5617                 message[1] = BOARD_RGHT   - 1 - j + '1';
5618                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5619             }
5620             SendToProgram(message, cps);
5621           }
5622         }
5623       }
5624
5625       SendToProgram("c\n", cps);
5626       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5627         bp = &boards[moveNum][i][BOARD_LEFT];
5628         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5629           if (((int) *bp != (int) EmptySquare)
5630               && ((int) *bp >= (int) BlackPawn)) {
5631             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5632                     AAA + j, ONE + i);
5633             if(message[0] == '+' || message[0] == '~') {
5634               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5635                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5636                         AAA + j, ONE + i);
5637             }
5638             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5639                 message[1] = BOARD_RGHT   - 1 - j + '1';
5640                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5641             }
5642             SendToProgram(message, cps);
5643           }
5644         }
5645       }
5646
5647       SendToProgram(".\n", cps);
5648     }
5649     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5650 }
5651
5652 static int autoQueen; // [HGM] oneclick
5653
5654 int
5655 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5656 {
5657     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5658     /* [HGM] add Shogi promotions */
5659     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5660     ChessSquare piece;
5661     ChessMove moveType;
5662     Boolean premove;
5663
5664     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5665     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5666
5667     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5668       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5669         return FALSE;
5670
5671     piece = boards[currentMove][fromY][fromX];
5672     if(gameInfo.variant == VariantShogi) {
5673         promotionZoneSize = BOARD_HEIGHT/3;
5674         highestPromotingPiece = (int)WhiteFerz;
5675     } else if(gameInfo.variant == VariantMakruk) {
5676         promotionZoneSize = 3;
5677     }
5678
5679     // next weed out all moves that do not touch the promotion zone at all
5680     if((int)piece >= BlackPawn) {
5681         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5682              return FALSE;
5683         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5684     } else {
5685         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5686            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5687     }
5688
5689     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5690
5691     // weed out mandatory Shogi promotions
5692     if(gameInfo.variant == VariantShogi) {
5693         if(piece >= BlackPawn) {
5694             if(toY == 0 && piece == BlackPawn ||
5695                toY == 0 && piece == BlackQueen ||
5696                toY <= 1 && piece == BlackKnight) {
5697                 *promoChoice = '+';
5698                 return FALSE;
5699             }
5700         } else {
5701             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5702                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5703                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5704                 *promoChoice = '+';
5705                 return FALSE;
5706             }
5707         }
5708     }
5709
5710     // weed out obviously illegal Pawn moves
5711     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5712         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5713         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5714         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5715         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5716         // note we are not allowed to test for valid (non-)capture, due to premove
5717     }
5718
5719     // we either have a choice what to promote to, or (in Shogi) whether to promote
5720     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5721         *promoChoice = PieceToChar(BlackFerz);  // no choice
5722         return FALSE;
5723     }
5724     // no sense asking what we must promote to if it is going to explode...
5725     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5726         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5727         return FALSE;
5728     }
5729     if(autoQueen) { // predetermined
5730         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5731              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5732         else *promoChoice = PieceToChar(BlackQueen);
5733         return FALSE;
5734     }
5735
5736     // suppress promotion popup on illegal moves that are not premoves
5737     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5738               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5739     if(appData.testLegality && !premove) {
5740         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5741                         fromY, fromX, toY, toX, NULLCHAR);
5742         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5743             return FALSE;
5744     }
5745
5746     return TRUE;
5747 }
5748
5749 int
5750 InPalace(row, column)
5751      int row, column;
5752 {   /* [HGM] for Xiangqi */
5753     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5754          column < (BOARD_WIDTH + 4)/2 &&
5755          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5756     return FALSE;
5757 }
5758
5759 int
5760 PieceForSquare (x, y)
5761      int x;
5762      int y;
5763 {
5764   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5765      return -1;
5766   else
5767      return boards[currentMove][y][x];
5768 }
5769
5770 int
5771 OKToStartUserMove(x, y)
5772      int x, y;
5773 {
5774     ChessSquare from_piece;
5775     int white_piece;
5776
5777     if (matchMode) return FALSE;
5778     if (gameMode == EditPosition) return TRUE;
5779
5780     if (x >= 0 && y >= 0)
5781       from_piece = boards[currentMove][y][x];
5782     else
5783       from_piece = EmptySquare;
5784
5785     if (from_piece == EmptySquare) return FALSE;
5786
5787     white_piece = (int)from_piece >= (int)WhitePawn &&
5788       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5789
5790     switch (gameMode) {
5791       case PlayFromGameFile:
5792       case AnalyzeFile:
5793       case TwoMachinesPlay:
5794       case EndOfGame:
5795         return FALSE;
5796
5797       case IcsObserving:
5798       case IcsIdle:
5799         return FALSE;
5800
5801       case MachinePlaysWhite:
5802       case IcsPlayingBlack:
5803         if (appData.zippyPlay) return FALSE;
5804         if (white_piece) {
5805             DisplayMoveError(_("You are playing Black"));
5806             return FALSE;
5807         }
5808         break;
5809
5810       case MachinePlaysBlack:
5811       case IcsPlayingWhite:
5812         if (appData.zippyPlay) return FALSE;
5813         if (!white_piece) {
5814             DisplayMoveError(_("You are playing White"));
5815             return FALSE;
5816         }
5817         break;
5818
5819       case EditGame:
5820         if (!white_piece && WhiteOnMove(currentMove)) {
5821             DisplayMoveError(_("It is White's turn"));
5822             return FALSE;
5823         }
5824         if (white_piece && !WhiteOnMove(currentMove)) {
5825             DisplayMoveError(_("It is Black's turn"));
5826             return FALSE;
5827         }
5828         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5829             /* Editing correspondence game history */
5830             /* Could disallow this or prompt for confirmation */
5831             cmailOldMove = -1;
5832         }
5833         break;
5834
5835       case BeginningOfGame:
5836         if (appData.icsActive) return FALSE;
5837         if (!appData.noChessProgram) {
5838             if (!white_piece) {
5839                 DisplayMoveError(_("You are playing White"));
5840                 return FALSE;
5841             }
5842         }
5843         break;
5844
5845       case Training:
5846         if (!white_piece && WhiteOnMove(currentMove)) {
5847             DisplayMoveError(_("It is White's turn"));
5848             return FALSE;
5849         }
5850         if (white_piece && !WhiteOnMove(currentMove)) {
5851             DisplayMoveError(_("It is Black's turn"));
5852             return FALSE;
5853         }
5854         break;
5855
5856       default:
5857       case IcsExamining:
5858         break;
5859     }
5860     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5861         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5862         && gameMode != AnalyzeFile && gameMode != Training) {
5863         DisplayMoveError(_("Displayed position is not current"));
5864         return FALSE;
5865     }
5866     return TRUE;
5867 }
5868
5869 Boolean
5870 OnlyMove(int *x, int *y, Boolean captures) {
5871     DisambiguateClosure cl;
5872     if (appData.zippyPlay) return FALSE;
5873     switch(gameMode) {
5874       case MachinePlaysBlack:
5875       case IcsPlayingWhite:
5876       case BeginningOfGame:
5877         if(!WhiteOnMove(currentMove)) return FALSE;
5878         break;
5879       case MachinePlaysWhite:
5880       case IcsPlayingBlack:
5881         if(WhiteOnMove(currentMove)) return FALSE;
5882         break;
5883       case EditGame:
5884         break;
5885       default:
5886         return FALSE;
5887     }
5888     cl.pieceIn = EmptySquare;
5889     cl.rfIn = *y;
5890     cl.ffIn = *x;
5891     cl.rtIn = -1;
5892     cl.ftIn = -1;
5893     cl.promoCharIn = NULLCHAR;
5894     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5895     if( cl.kind == NormalMove ||
5896         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5897         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5898         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5899       fromX = cl.ff;
5900       fromY = cl.rf;
5901       *x = cl.ft;
5902       *y = cl.rt;
5903       return TRUE;
5904     }
5905     if(cl.kind != ImpossibleMove) return FALSE;
5906     cl.pieceIn = EmptySquare;
5907     cl.rfIn = -1;
5908     cl.ffIn = -1;
5909     cl.rtIn = *y;
5910     cl.ftIn = *x;
5911     cl.promoCharIn = NULLCHAR;
5912     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5913     if( cl.kind == NormalMove ||
5914         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5915         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5916         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5917       fromX = cl.ff;
5918       fromY = cl.rf;
5919       *x = cl.ft;
5920       *y = cl.rt;
5921       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5922       return TRUE;
5923     }
5924     return FALSE;
5925 }
5926
5927 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5928 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5929 int lastLoadGameUseList = FALSE;
5930 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5931 ChessMove lastLoadGameStart = EndOfFile;
5932
5933 void
5934 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5935      int fromX, fromY, toX, toY;
5936      int promoChar;
5937 {
5938     ChessMove moveType;
5939     ChessSquare pdown, pup;
5940
5941     /* Check if the user is playing in turn.  This is complicated because we
5942        let the user "pick up" a piece before it is his turn.  So the piece he
5943        tried to pick up may have been captured by the time he puts it down!
5944        Therefore we use the color the user is supposed to be playing in this
5945        test, not the color of the piece that is currently on the starting
5946        square---except in EditGame mode, where the user is playing both
5947        sides; fortunately there the capture race can't happen.  (It can
5948        now happen in IcsExamining mode, but that's just too bad.  The user
5949        will get a somewhat confusing message in that case.)
5950        */
5951
5952     switch (gameMode) {
5953       case PlayFromGameFile:
5954       case AnalyzeFile:
5955       case TwoMachinesPlay:
5956       case EndOfGame:
5957       case IcsObserving:
5958       case IcsIdle:
5959         /* We switched into a game mode where moves are not accepted,
5960            perhaps while the mouse button was down. */
5961         return;
5962
5963       case MachinePlaysWhite:
5964         /* User is moving for Black */
5965         if (WhiteOnMove(currentMove)) {
5966             DisplayMoveError(_("It is White's turn"));
5967             return;
5968         }
5969         break;
5970
5971       case MachinePlaysBlack:
5972         /* User is moving for White */
5973         if (!WhiteOnMove(currentMove)) {
5974             DisplayMoveError(_("It is Black's turn"));
5975             return;
5976         }
5977         break;
5978
5979       case EditGame:
5980       case IcsExamining:
5981       case BeginningOfGame:
5982       case AnalyzeMode:
5983       case Training:
5984         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5985             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5986             /* User is moving for Black */
5987             if (WhiteOnMove(currentMove)) {
5988                 DisplayMoveError(_("It is White's turn"));
5989                 return;
5990             }
5991         } else {
5992             /* User is moving for White */
5993             if (!WhiteOnMove(currentMove)) {
5994                 DisplayMoveError(_("It is Black's turn"));
5995                 return;
5996             }
5997         }
5998         break;
5999
6000       case IcsPlayingBlack:
6001         /* User is moving for Black */
6002         if (WhiteOnMove(currentMove)) {
6003             if (!appData.premove) {
6004                 DisplayMoveError(_("It is White's turn"));
6005             } else if (toX >= 0 && toY >= 0) {
6006                 premoveToX = toX;
6007                 premoveToY = toY;
6008                 premoveFromX = fromX;
6009                 premoveFromY = fromY;
6010                 premovePromoChar = promoChar;
6011                 gotPremove = 1;
6012                 if (appData.debugMode)
6013                     fprintf(debugFP, "Got premove: fromX %d,"
6014                             "fromY %d, toX %d, toY %d\n",
6015                             fromX, fromY, toX, toY);
6016             }
6017             return;
6018         }
6019         break;
6020
6021       case IcsPlayingWhite:
6022         /* User is moving for White */
6023         if (!WhiteOnMove(currentMove)) {
6024             if (!appData.premove) {
6025                 DisplayMoveError(_("It is Black's turn"));
6026             } else if (toX >= 0 && toY >= 0) {
6027                 premoveToX = toX;
6028                 premoveToY = toY;
6029                 premoveFromX = fromX;
6030                 premoveFromY = fromY;
6031                 premovePromoChar = promoChar;
6032                 gotPremove = 1;
6033                 if (appData.debugMode)
6034                     fprintf(debugFP, "Got premove: fromX %d,"
6035                             "fromY %d, toX %d, toY %d\n",
6036                             fromX, fromY, toX, toY);
6037             }
6038             return;
6039         }
6040         break;
6041
6042       default:
6043         break;
6044
6045       case EditPosition:
6046         /* EditPosition, empty square, or different color piece;
6047            click-click move is possible */
6048         if (toX == -2 || toY == -2) {
6049             boards[0][fromY][fromX] = EmptySquare;
6050             DrawPosition(FALSE, boards[currentMove]);
6051             return;
6052         } else if (toX >= 0 && toY >= 0) {
6053             boards[0][toY][toX] = boards[0][fromY][fromX];
6054             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6055                 if(boards[0][fromY][0] != EmptySquare) {
6056                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6057                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6058                 }
6059             } else
6060             if(fromX == BOARD_RGHT+1) {
6061                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6062                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6063                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6064                 }
6065             } else
6066             boards[0][fromY][fromX] = EmptySquare;
6067             DrawPosition(FALSE, boards[currentMove]);
6068             return;
6069         }
6070         return;
6071     }
6072
6073     if(toX < 0 || toY < 0) return;
6074     pdown = boards[currentMove][fromY][fromX];
6075     pup = boards[currentMove][toY][toX];
6076
6077     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6078     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
6079          if( pup != EmptySquare ) return;
6080          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6081            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6082                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6083            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6084            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6085            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6086            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6087          fromY = DROP_RANK;
6088     }
6089
6090     /* [HGM] always test for legality, to get promotion info */
6091     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6092                                          fromY, fromX, toY, toX, promoChar);
6093     /* [HGM] but possibly ignore an IllegalMove result */
6094     if (appData.testLegality) {
6095         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6096             DisplayMoveError(_("Illegal move"));
6097             return;
6098         }
6099     }
6100
6101     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6102 }
6103
6104 /* Common tail of UserMoveEvent and DropMenuEvent */
6105 int
6106 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6107      ChessMove moveType;
6108      int fromX, fromY, toX, toY;
6109      /*char*/int promoChar;
6110 {
6111     char *bookHit = 0;
6112
6113     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6114         // [HGM] superchess: suppress promotions to non-available piece
6115         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6116         if(WhiteOnMove(currentMove)) {
6117             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6118         } else {
6119             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6120         }
6121     }
6122
6123     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6124        move type in caller when we know the move is a legal promotion */
6125     if(moveType == NormalMove && promoChar)
6126         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6127
6128     /* [HGM] <popupFix> The following if has been moved here from
6129        UserMoveEvent(). Because it seemed to belong here (why not allow
6130        piece drops in training games?), and because it can only be
6131        performed after it is known to what we promote. */
6132     if (gameMode == Training) {
6133       /* compare the move played on the board to the next move in the
6134        * game. If they match, display the move and the opponent's response.
6135        * If they don't match, display an error message.
6136        */
6137       int saveAnimate;
6138       Board testBoard;
6139       CopyBoard(testBoard, boards[currentMove]);
6140       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6141
6142       if (CompareBoards(testBoard, boards[currentMove+1])) {
6143         ForwardInner(currentMove+1);
6144
6145         /* Autoplay the opponent's response.
6146          * if appData.animate was TRUE when Training mode was entered,
6147          * the response will be animated.
6148          */
6149         saveAnimate = appData.animate;
6150         appData.animate = animateTraining;
6151         ForwardInner(currentMove+1);
6152         appData.animate = saveAnimate;
6153
6154         /* check for the end of the game */
6155         if (currentMove >= forwardMostMove) {
6156           gameMode = PlayFromGameFile;
6157           ModeHighlight();
6158           SetTrainingModeOff();
6159           DisplayInformation(_("End of game"));
6160         }
6161       } else {
6162         DisplayError(_("Incorrect move"), 0);
6163       }
6164       return 1;
6165     }
6166
6167   /* Ok, now we know that the move is good, so we can kill
6168      the previous line in Analysis Mode */
6169   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6170                                 && currentMove < forwardMostMove) {
6171     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6172     else forwardMostMove = currentMove;
6173   }
6174
6175   /* If we need the chess program but it's dead, restart it */
6176   ResurrectChessProgram();
6177
6178   /* A user move restarts a paused game*/
6179   if (pausing)
6180     PauseEvent();
6181
6182   thinkOutput[0] = NULLCHAR;
6183
6184   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6185
6186   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6187
6188   if (gameMode == BeginningOfGame) {
6189     if (appData.noChessProgram) {
6190       gameMode = EditGame;
6191       SetGameInfo();
6192     } else {
6193       char buf[MSG_SIZ];
6194       gameMode = MachinePlaysBlack;
6195       StartClocks();
6196       SetGameInfo();
6197       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6198       DisplayTitle(buf);
6199       if (first.sendName) {
6200         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6201         SendToProgram(buf, &first);
6202       }
6203       StartClocks();
6204     }
6205     ModeHighlight();
6206   }
6207
6208   /* Relay move to ICS or chess engine */
6209   if (appData.icsActive) {
6210     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6211         gameMode == IcsExamining) {
6212       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6213         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6214         SendToICS("draw ");
6215         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6216       }
6217       // also send plain move, in case ICS does not understand atomic claims
6218       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6219       ics_user_moved = 1;
6220     }
6221   } else {
6222     if (first.sendTime && (gameMode == BeginningOfGame ||
6223                            gameMode == MachinePlaysWhite ||
6224                            gameMode == MachinePlaysBlack)) {
6225       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6226     }
6227     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6228          // [HGM] book: if program might be playing, let it use book
6229         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6230         first.maybeThinking = TRUE;
6231     } else SendMoveToProgram(forwardMostMove-1, &first);
6232     if (currentMove == cmailOldMove + 1) {
6233       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6234     }
6235   }
6236
6237   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6238
6239   switch (gameMode) {
6240   case EditGame:
6241     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6242     case MT_NONE:
6243     case MT_CHECK:
6244       break;
6245     case MT_CHECKMATE:
6246     case MT_STAINMATE:
6247       if (WhiteOnMove(currentMove)) {
6248         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6249       } else {
6250         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6251       }
6252       break;
6253     case MT_STALEMATE:
6254       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6255       break;
6256     }
6257     break;
6258
6259   case MachinePlaysBlack:
6260   case MachinePlaysWhite:
6261     /* disable certain menu options while machine is thinking */
6262     SetMachineThinkingEnables();
6263     break;
6264
6265   default:
6266     break;
6267   }
6268
6269   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6270
6271   if(bookHit) { // [HGM] book: simulate book reply
6272         static char bookMove[MSG_SIZ]; // a bit generous?
6273
6274         programStats.nodes = programStats.depth = programStats.time =
6275         programStats.score = programStats.got_only_move = 0;
6276         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6277
6278         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6279         strcat(bookMove, bookHit);
6280         HandleMachineMove(bookMove, &first);
6281   }
6282   return 1;
6283 }
6284
6285 void
6286 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6287      Board board;
6288      int flags;
6289      ChessMove kind;
6290      int rf, ff, rt, ft;
6291      VOIDSTAR closure;
6292 {
6293     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6294     Markers *m = (Markers *) closure;
6295     if(rf == fromY && ff == fromX)
6296         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6297                          || kind == WhiteCapturesEnPassant
6298                          || kind == BlackCapturesEnPassant);
6299     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6300 }
6301
6302 void
6303 MarkTargetSquares(int clear)
6304 {
6305   int x, y;
6306   if(!appData.markers || !appData.highlightDragging ||
6307      !appData.testLegality || gameMode == EditPosition) return;
6308   if(clear) {
6309     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6310   } else {
6311     int capt = 0;
6312     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6313     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6314       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6315       if(capt)
6316       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6317     }
6318   }
6319   DrawPosition(TRUE, NULL);
6320 }
6321
6322 int
6323 Explode(Board board, int fromX, int fromY, int toX, int toY)
6324 {
6325     if(gameInfo.variant == VariantAtomic &&
6326        (board[toY][toX] != EmptySquare ||                     // capture?
6327         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6328                          board[fromY][fromX] == BlackPawn   )
6329       )) {
6330         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6331         return TRUE;
6332     }
6333     return FALSE;
6334 }
6335
6336 void LeftClick(ClickType clickType, int xPix, int yPix)
6337 {
6338     int x, y;
6339     Boolean saveAnimate;
6340     static int second = 0, promotionChoice = 0, dragging = 0;
6341     char promoChoice = NULLCHAR;
6342
6343     if(appData.seekGraph && appData.icsActive && loggedOn &&
6344         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6345         SeekGraphClick(clickType, xPix, yPix, 0);
6346         return;
6347     }
6348
6349     if (clickType == Press) ErrorPopDown();
6350     MarkTargetSquares(1);
6351
6352     x = EventToSquare(xPix, BOARD_WIDTH);
6353     y = EventToSquare(yPix, BOARD_HEIGHT);
6354     if (!flipView && y >= 0) {
6355         y = BOARD_HEIGHT - 1 - y;
6356     }
6357     if (flipView && x >= 0) {
6358         x = BOARD_WIDTH - 1 - x;
6359     }
6360
6361     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6362         if(clickType == Release) return; // ignore upclick of click-click destination
6363         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6364         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6365         if(gameInfo.holdingsWidth &&
6366                 (WhiteOnMove(currentMove)
6367                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6368                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6369             // click in right holdings, for determining promotion piece
6370             ChessSquare p = boards[currentMove][y][x];
6371             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6372             if(p != EmptySquare) {
6373                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6374                 fromX = fromY = -1;
6375                 return;
6376             }
6377         }
6378         DrawPosition(FALSE, boards[currentMove]);
6379         return;
6380     }
6381
6382     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6383     if(clickType == Press
6384             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6385               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6386               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6387         return;
6388
6389     autoQueen = appData.alwaysPromoteToQueen;
6390
6391     if (fromX == -1) {
6392       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6393         if (clickType == Press) {
6394             /* First square */
6395             if (OKToStartUserMove(x, y)) {
6396                 fromX = x;
6397                 fromY = y;
6398                 second = 0;
6399                 MarkTargetSquares(0);
6400                 DragPieceBegin(xPix, yPix); dragging = 1;
6401                 if (appData.highlightDragging) {
6402                     SetHighlights(x, y, -1, -1);
6403                 }
6404             }
6405         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6406             DragPieceEnd(xPix, yPix); dragging = 0;
6407             DrawPosition(FALSE, NULL);
6408         }
6409         return;
6410       }
6411     }
6412
6413     /* fromX != -1 */
6414     if (clickType == Press && gameMode != EditPosition) {
6415         ChessSquare fromP;
6416         ChessSquare toP;
6417         int frc;
6418
6419         // ignore off-board to clicks
6420         if(y < 0 || x < 0) return;
6421
6422         /* Check if clicking again on the same color piece */
6423         fromP = boards[currentMove][fromY][fromX];
6424         toP = boards[currentMove][y][x];
6425         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6426         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6427              WhitePawn <= toP && toP <= WhiteKing &&
6428              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6429              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6430             (BlackPawn <= fromP && fromP <= BlackKing &&
6431              BlackPawn <= toP && toP <= BlackKing &&
6432              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6433              !(fromP == BlackKing && toP == BlackRook && frc))) {
6434             /* Clicked again on same color piece -- changed his mind */
6435             second = (x == fromX && y == fromY);
6436            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6437             if (appData.highlightDragging) {
6438                 SetHighlights(x, y, -1, -1);
6439             } else {
6440                 ClearHighlights();
6441             }
6442             if (OKToStartUserMove(x, y)) {
6443                 fromX = x;
6444                 fromY = y; dragging = 1;
6445                 MarkTargetSquares(0);
6446                 DragPieceBegin(xPix, yPix);
6447             }
6448             return;
6449            }
6450         }
6451         // ignore clicks on holdings
6452         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6453     }
6454
6455     if (clickType == Release && x == fromX && y == fromY) {
6456         DragPieceEnd(xPix, yPix); dragging = 0;
6457         if (appData.animateDragging) {
6458             /* Undo animation damage if any */
6459             DrawPosition(FALSE, NULL);
6460         }
6461         if (second) {
6462             /* Second up/down in same square; just abort move */
6463             second = 0;
6464             fromX = fromY = -1;
6465             ClearHighlights();
6466             gotPremove = 0;
6467             ClearPremoveHighlights();
6468         } else {
6469             /* First upclick in same square; start click-click mode */
6470             SetHighlights(x, y, -1, -1);
6471         }
6472         return;
6473     }
6474
6475     /* we now have a different from- and (possibly off-board) to-square */
6476     /* Completed move */
6477     toX = x;
6478     toY = y;
6479     saveAnimate = appData.animate;
6480     if (clickType == Press) {
6481         /* Finish clickclick move */
6482         if (appData.animate || appData.highlightLastMove) {
6483             SetHighlights(fromX, fromY, toX, toY);
6484         } else {
6485             ClearHighlights();
6486         }
6487     } else {
6488         /* Finish drag move */
6489         if (appData.highlightLastMove) {
6490             SetHighlights(fromX, fromY, toX, toY);
6491         } else {
6492             ClearHighlights();
6493         }
6494         DragPieceEnd(xPix, yPix); dragging = 0;
6495         /* Don't animate move and drag both */
6496         appData.animate = FALSE;
6497     }
6498
6499     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6500     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6501         ChessSquare piece = boards[currentMove][fromY][fromX];
6502         if(gameMode == EditPosition && piece != EmptySquare &&
6503            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6504             int n;
6505
6506             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6507                 n = PieceToNumber(piece - (int)BlackPawn);
6508                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6509                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6510                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6511             } else
6512             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6513                 n = PieceToNumber(piece);
6514                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6515                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6516                 boards[currentMove][n][BOARD_WIDTH-2]++;
6517             }
6518             boards[currentMove][fromY][fromX] = EmptySquare;
6519         }
6520         ClearHighlights();
6521         fromX = fromY = -1;
6522         DrawPosition(TRUE, boards[currentMove]);
6523         return;
6524     }
6525
6526     // off-board moves should not be highlighted
6527     if(x < 0 || x < 0) ClearHighlights();
6528
6529     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6530         SetHighlights(fromX, fromY, toX, toY);
6531         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6532             // [HGM] super: promotion to captured piece selected from holdings
6533             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6534             promotionChoice = TRUE;
6535             // kludge follows to temporarily execute move on display, without promoting yet
6536             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6537             boards[currentMove][toY][toX] = p;
6538             DrawPosition(FALSE, boards[currentMove]);
6539             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6540             boards[currentMove][toY][toX] = q;
6541             DisplayMessage("Click in holdings to choose piece", "");
6542             return;
6543         }
6544         PromotionPopUp();
6545     } else {
6546         int oldMove = currentMove;
6547         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6548         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6549         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6550         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6551            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6552             DrawPosition(TRUE, boards[currentMove]);
6553         fromX = fromY = -1;
6554     }
6555     appData.animate = saveAnimate;
6556     if (appData.animate || appData.animateDragging) {
6557         /* Undo animation damage if needed */
6558         DrawPosition(FALSE, NULL);
6559     }
6560 }
6561
6562 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6563 {   // front-end-free part taken out of PieceMenuPopup
6564     int whichMenu; int xSqr, ySqr;
6565
6566     if(seekGraphUp) { // [HGM] seekgraph
6567         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6568         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6569         return -2;
6570     }
6571
6572     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6573          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6574         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6575         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6576         if(action == Press)   {
6577             originalFlip = flipView;
6578             flipView = !flipView; // temporarily flip board to see game from partners perspective
6579             DrawPosition(TRUE, partnerBoard);
6580             DisplayMessage(partnerStatus, "");
6581             partnerUp = TRUE;
6582         } else if(action == Release) {
6583             flipView = originalFlip;
6584             DrawPosition(TRUE, boards[currentMove]);
6585             partnerUp = FALSE;
6586         }
6587         return -2;
6588     }
6589
6590     xSqr = EventToSquare(x, BOARD_WIDTH);
6591     ySqr = EventToSquare(y, BOARD_HEIGHT);
6592     if (action == Release) UnLoadPV(); // [HGM] pv
6593     if (action != Press) return -2; // return code to be ignored
6594     switch (gameMode) {
6595       case IcsExamining:
6596         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6597       case EditPosition:
6598         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6599         if (xSqr < 0 || ySqr < 0) return -1;\r
6600         whichMenu = 0; // edit-position menu
6601         break;
6602       case IcsObserving:
6603         if(!appData.icsEngineAnalyze) return -1;
6604       case IcsPlayingWhite:
6605       case IcsPlayingBlack:
6606         if(!appData.zippyPlay) goto noZip;
6607       case AnalyzeMode:
6608       case AnalyzeFile:
6609       case MachinePlaysWhite:
6610       case MachinePlaysBlack:
6611       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6612         if (!appData.dropMenu) {
6613           LoadPV(x, y);
6614           return 2; // flag front-end to grab mouse events
6615         }
6616         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6617            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6618       case EditGame:
6619       noZip:
6620         if (xSqr < 0 || ySqr < 0) return -1;
6621         if (!appData.dropMenu || appData.testLegality &&
6622             gameInfo.variant != VariantBughouse &&
6623             gameInfo.variant != VariantCrazyhouse) return -1;
6624         whichMenu = 1; // drop menu
6625         break;
6626       default:
6627         return -1;
6628     }
6629
6630     if (((*fromX = xSqr) < 0) ||
6631         ((*fromY = ySqr) < 0)) {
6632         *fromX = *fromY = -1;
6633         return -1;
6634     }
6635     if (flipView)
6636       *fromX = BOARD_WIDTH - 1 - *fromX;
6637     else
6638       *fromY = BOARD_HEIGHT - 1 - *fromY;
6639
6640     return whichMenu;
6641 }
6642
6643 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6644 {
6645 //    char * hint = lastHint;
6646     FrontEndProgramStats stats;
6647
6648     stats.which = cps == &first ? 0 : 1;
6649     stats.depth = cpstats->depth;
6650     stats.nodes = cpstats->nodes;
6651     stats.score = cpstats->score;
6652     stats.time = cpstats->time;
6653     stats.pv = cpstats->movelist;
6654     stats.hint = lastHint;
6655     stats.an_move_index = 0;
6656     stats.an_move_count = 0;
6657
6658     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6659         stats.hint = cpstats->move_name;
6660         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6661         stats.an_move_count = cpstats->nr_moves;
6662     }
6663
6664     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
6665
6666     SetProgramStats( &stats );
6667 }
6668
6669 void
6670 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6671 {       // count all piece types
6672         int p, f, r;
6673         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6674         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6675         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6676                 p = board[r][f];
6677                 pCnt[p]++;
6678                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6679                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6680                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6681                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6682                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6683                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6684         }
6685 }
6686
6687 int
6688 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6689 {
6690         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6691         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6692
6693         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6694         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6695         if(myPawns == 2 && nMine == 3) // KPP
6696             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6697         if(myPawns == 1 && nMine == 2) // KP
6698             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6699         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6700             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6701         if(myPawns) return FALSE;
6702         if(pCnt[WhiteRook+side])
6703             return pCnt[BlackRook-side] ||
6704                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6705                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6706                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6707         if(pCnt[WhiteCannon+side]) {
6708             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6709             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6710         }
6711         if(pCnt[WhiteKnight+side])
6712             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6713         return FALSE;
6714 }
6715
6716 int
6717 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6718 {
6719         VariantClass v = gameInfo.variant;
6720
6721         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6722         if(v == VariantShatranj) return TRUE; // always winnable through baring
6723         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6724         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6725
6726         if(v == VariantXiangqi) {
6727                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6728
6729                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6730                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6731                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6732                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6733                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6734                 if(stale) // we have at least one last-rank P plus perhaps C
6735                     return majors // KPKX
6736                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6737                 else // KCA*E*
6738                     return pCnt[WhiteFerz+side] // KCAK
6739                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6740                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6741                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6742
6743         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6744                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6745
6746                 if(nMine == 1) return FALSE; // bare King
6747                 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
6748                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6749                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6750                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6751                 if(pCnt[WhiteKnight+side])
6752                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6753                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6754                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6755                 if(nBishops)
6756                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6757                 if(pCnt[WhiteAlfil+side])
6758                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6759                 if(pCnt[WhiteWazir+side])
6760                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6761         }
6762
6763         return TRUE;
6764 }
6765
6766 int
6767 Adjudicate(ChessProgramState *cps)
6768 {       // [HGM] some adjudications useful with buggy engines
6769         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6770         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6771         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6772         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6773         int k, count = 0; static int bare = 1;
6774         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6775         Boolean canAdjudicate = !appData.icsActive;
6776
6777         // most tests only when we understand the game, i.e. legality-checking on
6778             if( appData.testLegality )
6779             {   /* [HGM] Some more adjudications for obstinate engines */
6780                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6781                 static int moveCount = 6;
6782                 ChessMove result;
6783                 char *reason = NULL;
6784
6785                 /* Count what is on board. */
6786                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6787
6788                 /* Some material-based adjudications that have to be made before stalemate test */
6789                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6790                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6791                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6792                      if(canAdjudicate && appData.checkMates) {
6793                          if(engineOpponent)
6794                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6795                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6796                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6797                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6798                          return 1;
6799                      }
6800                 }
6801
6802                 /* Bare King in Shatranj (loses) or Losers (wins) */
6803                 if( nrW == 1 || nrB == 1) {
6804                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6805                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6806                      if(canAdjudicate && appData.checkMates) {
6807                          if(engineOpponent)
6808                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6809                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6810                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6811                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6812                          return 1;
6813                      }
6814                   } else
6815                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6816                   {    /* bare King */
6817                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6818                         if(canAdjudicate && appData.checkMates) {
6819                             /* but only adjudicate if adjudication enabled */
6820                             if(engineOpponent)
6821                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6822                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6823                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6824                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6825                             return 1;
6826                         }
6827                   }
6828                 } else bare = 1;
6829
6830
6831             // don't wait for engine to announce game end if we can judge ourselves
6832             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6833               case MT_CHECK:
6834                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6835                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6836                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6837                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6838                             checkCnt++;
6839                         if(checkCnt >= 2) {
6840                             reason = "Xboard adjudication: 3rd check";
6841                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6842                             break;
6843                         }
6844                     }
6845                 }
6846               case MT_NONE:
6847               default:
6848                 break;
6849               case MT_STALEMATE:
6850               case MT_STAINMATE:
6851                 reason = "Xboard adjudication: Stalemate";
6852                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6853                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6854                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6855                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6856                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6857                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6858                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6859                                                                         EP_CHECKMATE : EP_WINS);
6860                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6861                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6862                 }
6863                 break;
6864               case MT_CHECKMATE:
6865                 reason = "Xboard adjudication: Checkmate";
6866                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6867                 break;
6868             }
6869
6870                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6871                     case EP_STALEMATE:
6872                         result = GameIsDrawn; break;
6873                     case EP_CHECKMATE:
6874                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6875                     case EP_WINS:
6876                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6877                     default:
6878                         result = EndOfFile;
6879                 }
6880                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6881                     if(engineOpponent)
6882                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6883                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6884                     GameEnds( result, reason, GE_XBOARD );
6885                     return 1;
6886                 }
6887
6888                 /* Next absolutely insufficient mating material. */
6889                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6890                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6891                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6892
6893                      /* always flag draws, for judging claims */
6894                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6895
6896                      if(canAdjudicate && appData.materialDraws) {
6897                          /* but only adjudicate them if adjudication enabled */
6898                          if(engineOpponent) {
6899                            SendToProgram("force\n", engineOpponent); // suppress reply
6900                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6901                          }
6902                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6903                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6904                          return 1;
6905                      }
6906                 }
6907
6908                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6909                 if(gameInfo.variant == VariantXiangqi ?
6910                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6911                  : nrW + nrB == 4 &&
6912                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6913                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6914                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6915                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6916                    ) ) {
6917                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6918                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6919                           if(engineOpponent) {
6920                             SendToProgram("force\n", engineOpponent); // suppress reply
6921                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6922                           }
6923                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6924                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6925                           return 1;
6926                      }
6927                 } else moveCount = 6;
6928             }
6929         if (appData.debugMode) { int i;
6930             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6931                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6932                     appData.drawRepeats);
6933             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6934               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6935
6936         }
6937
6938         // Repetition draws and 50-move rule can be applied independently of legality testing
6939
6940                 /* Check for rep-draws */
6941                 count = 0;
6942                 for(k = forwardMostMove-2;
6943                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6944                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6945                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6946                     k-=2)
6947                 {   int rights=0;
6948                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6949                         /* compare castling rights */
6950                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6951                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6952                                 rights++; /* King lost rights, while rook still had them */
6953                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6954                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6955                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6956                                    rights++; /* but at least one rook lost them */
6957                         }
6958                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6959                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6960                                 rights++;
6961                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6962                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6963                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6964                                    rights++;
6965                         }
6966                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6967                             && appData.drawRepeats > 1) {
6968                              /* adjudicate after user-specified nr of repeats */
6969                              int result = GameIsDrawn;
6970                              char *details = "XBoard adjudication: repetition draw";
6971                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6972                                 // [HGM] xiangqi: check for forbidden perpetuals
6973                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6974                                 for(m=forwardMostMove; m>k; m-=2) {
6975                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6976                                         ourPerpetual = 0; // the current mover did not always check
6977                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6978                                         hisPerpetual = 0; // the opponent did not always check
6979                                 }
6980                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6981                                                                         ourPerpetual, hisPerpetual);
6982                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6983                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6984                                     details = "Xboard adjudication: perpetual checking";
6985                                 } else
6986                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6987                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6988                                 } else
6989                                 // Now check for perpetual chases
6990                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6991                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6992                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6993                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6994                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6995                                         details = "Xboard adjudication: perpetual chasing";
6996                                     } else
6997                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6998                                         break; // Abort repetition-checking loop.
6999                                 }
7000                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7001                              }
7002                              if(engineOpponent) {
7003                                SendToProgram("force\n", engineOpponent); // suppress reply
7004                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7005                              }
7006                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7007                              GameEnds( result, details, GE_XBOARD );
7008                              return 1;
7009                         }
7010                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7011                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7012                     }
7013                 }
7014
7015                 /* Now we test for 50-move draws. Determine ply count */
7016                 count = forwardMostMove;
7017                 /* look for last irreversble move */
7018                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7019                     count--;
7020                 /* if we hit starting position, add initial plies */
7021                 if( count == backwardMostMove )
7022                     count -= initialRulePlies;
7023                 count = forwardMostMove - count;
7024                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7025                         // adjust reversible move counter for checks in Xiangqi
7026                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7027                         if(i < backwardMostMove) i = backwardMostMove;
7028                         while(i <= forwardMostMove) {
7029                                 lastCheck = inCheck; // check evasion does not count
7030                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7031                                 if(inCheck || lastCheck) count--; // check does not count
7032                                 i++;
7033                         }
7034                 }
7035                 if( count >= 100)
7036                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7037                          /* this is used to judge if draw claims are legal */
7038                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7039                          if(engineOpponent) {
7040                            SendToProgram("force\n", engineOpponent); // suppress reply
7041                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7042                          }
7043                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7044                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7045                          return 1;
7046                 }
7047
7048                 /* if draw offer is pending, treat it as a draw claim
7049                  * when draw condition present, to allow engines a way to
7050                  * claim draws before making their move to avoid a race
7051                  * condition occurring after their move
7052                  */
7053                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7054                          char *p = NULL;
7055                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7056                              p = "Draw claim: 50-move rule";
7057                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7058                              p = "Draw claim: 3-fold repetition";
7059                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7060                              p = "Draw claim: insufficient mating material";
7061                          if( p != NULL && canAdjudicate) {
7062                              if(engineOpponent) {
7063                                SendToProgram("force\n", engineOpponent); // suppress reply
7064                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7065                              }
7066                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7067                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7068                              return 1;
7069                          }
7070                 }
7071
7072                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7073                     if(engineOpponent) {
7074                       SendToProgram("force\n", engineOpponent); // suppress reply
7075                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7076                     }
7077                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7078                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7079                     return 1;
7080                 }
7081         return 0;
7082 }
7083
7084 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7085 {   // [HGM] book: this routine intercepts moves to simulate book replies
7086     char *bookHit = NULL;
7087
7088     //first determine if the incoming move brings opponent into his book
7089     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7090         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7091     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7092     if(bookHit != NULL && !cps->bookSuspend) {
7093         // make sure opponent is not going to reply after receiving move to book position
7094         SendToProgram("force\n", cps);
7095         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7096     }
7097     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7098     // now arrange restart after book miss
7099     if(bookHit) {
7100         // after a book hit we never send 'go', and the code after the call to this routine
7101         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7102         char buf[MSG_SIZ];
7103         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7104         SendToProgram(buf, cps);
7105         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7106     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7107         SendToProgram("go\n", cps);
7108         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7109     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7110         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7111             SendToProgram("go\n", cps);
7112         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7113     }
7114     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7115 }
7116
7117 char *savedMessage;
7118 ChessProgramState *savedState;
7119 void DeferredBookMove(void)
7120 {
7121         if(savedState->lastPing != savedState->lastPong)
7122                     ScheduleDelayedEvent(DeferredBookMove, 10);
7123         else
7124         HandleMachineMove(savedMessage, savedState);
7125 }
7126
7127 void
7128 HandleMachineMove(message, cps)
7129      char *message;
7130      ChessProgramState *cps;
7131 {
7132     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7133     char realname[MSG_SIZ];
7134     int fromX, fromY, toX, toY;
7135     ChessMove moveType;
7136     char promoChar;
7137     char *p;
7138     int machineWhite;
7139     char *bookHit;
7140
7141     cps->userError = 0;
7142
7143 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7144     /*
7145      * Kludge to ignore BEL characters
7146      */
7147     while (*message == '\007') message++;
7148
7149     /*
7150      * [HGM] engine debug message: ignore lines starting with '#' character
7151      */
7152     if(cps->debug && *message == '#') return;
7153
7154     /*
7155      * Look for book output
7156      */
7157     if (cps == &first && bookRequested) {
7158         if (message[0] == '\t' || message[0] == ' ') {
7159             /* Part of the book output is here; append it */
7160             strcat(bookOutput, message);
7161             strcat(bookOutput, "  \n");
7162             return;
7163         } else if (bookOutput[0] != NULLCHAR) {
7164             /* All of book output has arrived; display it */
7165             char *p = bookOutput;
7166             while (*p != NULLCHAR) {
7167                 if (*p == '\t') *p = ' ';
7168                 p++;
7169             }
7170             DisplayInformation(bookOutput);
7171             bookRequested = FALSE;
7172             /* Fall through to parse the current output */
7173         }
7174     }
7175
7176     /*
7177      * Look for machine move.
7178      */
7179     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7180         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7181     {
7182         /* This method is only useful on engines that support ping */
7183         if (cps->lastPing != cps->lastPong) {
7184           if (gameMode == BeginningOfGame) {
7185             /* Extra move from before last new; ignore */
7186             if (appData.debugMode) {
7187                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7188             }
7189           } else {
7190             if (appData.debugMode) {
7191                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7192                         cps->which, gameMode);
7193             }
7194
7195             SendToProgram("undo\n", cps);
7196           }
7197           return;
7198         }
7199
7200         switch (gameMode) {
7201           case BeginningOfGame:
7202             /* Extra move from before last reset; ignore */
7203             if (appData.debugMode) {
7204                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7205             }
7206             return;
7207
7208           case EndOfGame:
7209           case IcsIdle:
7210           default:
7211             /* Extra move after we tried to stop.  The mode test is
7212                not a reliable way of detecting this problem, but it's
7213                the best we can do on engines that don't support ping.
7214             */
7215             if (appData.debugMode) {
7216                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7217                         cps->which, gameMode);
7218             }
7219             SendToProgram("undo\n", cps);
7220             return;
7221
7222           case MachinePlaysWhite:
7223           case IcsPlayingWhite:
7224             machineWhite = TRUE;
7225             break;
7226
7227           case MachinePlaysBlack:
7228           case IcsPlayingBlack:
7229             machineWhite = FALSE;
7230             break;
7231
7232           case TwoMachinesPlay:
7233             machineWhite = (cps->twoMachinesColor[0] == 'w');
7234             break;
7235         }
7236         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7237             if (appData.debugMode) {
7238                 fprintf(debugFP,
7239                         "Ignoring move out of turn by %s, gameMode %d"
7240                         ", forwardMost %d\n",
7241                         cps->which, gameMode, forwardMostMove);
7242             }
7243             return;
7244         }
7245
7246     if (appData.debugMode) { int f = forwardMostMove;
7247         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7248                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7249                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7250     }
7251         if(cps->alphaRank) AlphaRank(machineMove, 4);
7252         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7253                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7254             /* Machine move could not be parsed; ignore it. */
7255           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7256                     machineMove, cps->which);
7257             DisplayError(buf1, 0);
7258             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7259                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7260             if (gameMode == TwoMachinesPlay) {
7261               GameEnds(machineWhite ? BlackWins : WhiteWins,
7262                        buf1, GE_XBOARD);
7263             }
7264             return;
7265         }
7266
7267         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7268         /* So we have to redo legality test with true e.p. status here,  */
7269         /* to make sure an illegal e.p. capture does not slip through,   */
7270         /* to cause a forfeit on a justified illegal-move complaint      */
7271         /* of the opponent.                                              */
7272         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7273            ChessMove moveType;
7274            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7275                              fromY, fromX, toY, toX, promoChar);
7276             if (appData.debugMode) {
7277                 int i;
7278                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7279                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7280                 fprintf(debugFP, "castling rights\n");
7281             }
7282             if(moveType == IllegalMove) {
7283               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7284                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7285                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7286                            buf1, GE_XBOARD);
7287                 return;
7288            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7289            /* [HGM] Kludge to handle engines that send FRC-style castling
7290               when they shouldn't (like TSCP-Gothic) */
7291            switch(moveType) {
7292              case WhiteASideCastleFR:
7293              case BlackASideCastleFR:
7294                toX+=2;
7295                currentMoveString[2]++;
7296                break;
7297              case WhiteHSideCastleFR:
7298              case BlackHSideCastleFR:
7299                toX--;
7300                currentMoveString[2]--;
7301                break;
7302              default: ; // nothing to do, but suppresses warning of pedantic compilers
7303            }
7304         }
7305         hintRequested = FALSE;
7306         lastHint[0] = NULLCHAR;
7307         bookRequested = FALSE;
7308         /* Program may be pondering now */
7309         cps->maybeThinking = TRUE;
7310         if (cps->sendTime == 2) cps->sendTime = 1;
7311         if (cps->offeredDraw) cps->offeredDraw--;
7312
7313         /* currentMoveString is set as a side-effect of ParseOneMove */
7314         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7315         strcat(machineMove, "\n");
7316         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7317
7318         /* [AS] Save move info*/
7319         pvInfoList[ forwardMostMove ].score = programStats.score;
7320         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7321         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7322
7323         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7324
7325         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7326         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7327             int count = 0;
7328
7329             while( count < adjudicateLossPlies ) {
7330                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7331
7332                 if( count & 1 ) {
7333                     score = -score; /* Flip score for winning side */
7334                 }
7335
7336                 if( score > adjudicateLossThreshold ) {
7337                     break;
7338                 }
7339
7340                 count++;
7341             }
7342
7343             if( count >= adjudicateLossPlies ) {
7344                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7345
7346                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7347                     "Xboard adjudication",
7348                     GE_XBOARD );
7349
7350                 return;
7351             }
7352         }
7353
7354         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7355
7356 #if ZIPPY
7357         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7358             first.initDone) {
7359           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7360                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7361                 SendToICS("draw ");
7362                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7363           }
7364           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7365           ics_user_moved = 1;
7366           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7367                 char buf[3*MSG_SIZ];
7368
7369                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7370                         programStats.score / 100.,
7371                         programStats.depth,
7372                         programStats.time / 100.,
7373                         (unsigned int)programStats.nodes,
7374                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7375                         programStats.movelist);
7376                 SendToICS(buf);
7377 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7378           }
7379         }
7380 #endif
7381
7382         /* [AS] Clear stats for next move */
7383         ClearProgramStats();
7384         thinkOutput[0] = NULLCHAR;
7385         hiddenThinkOutputState = 0;
7386
7387         bookHit = NULL;
7388         if (gameMode == TwoMachinesPlay) {
7389             /* [HGM] relaying draw offers moved to after reception of move */
7390             /* and interpreting offer as claim if it brings draw condition */
7391             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7392                 SendToProgram("draw\n", cps->other);
7393             }
7394             if (cps->other->sendTime) {
7395                 SendTimeRemaining(cps->other,
7396                                   cps->other->twoMachinesColor[0] == 'w');
7397             }
7398             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7399             if (firstMove && !bookHit) {
7400                 firstMove = FALSE;
7401                 if (cps->other->useColors) {
7402                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7403                 }
7404                 SendToProgram("go\n", cps->other);
7405             }
7406             cps->other->maybeThinking = TRUE;
7407         }
7408
7409         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7410
7411         if (!pausing && appData.ringBellAfterMoves) {
7412             RingBell();
7413         }
7414
7415         /*
7416          * Reenable menu items that were disabled while
7417          * machine was thinking
7418          */
7419         if (gameMode != TwoMachinesPlay)
7420             SetUserThinkingEnables();
7421
7422         // [HGM] book: after book hit opponent has received move and is now in force mode
7423         // force the book reply into it, and then fake that it outputted this move by jumping
7424         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7425         if(bookHit) {
7426                 static char bookMove[MSG_SIZ]; // a bit generous?
7427
7428                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7429                 strcat(bookMove, bookHit);
7430                 message = bookMove;
7431                 cps = cps->other;
7432                 programStats.nodes = programStats.depth = programStats.time =
7433                 programStats.score = programStats.got_only_move = 0;
7434                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7435
7436                 if(cps->lastPing != cps->lastPong) {
7437                     savedMessage = message; // args for deferred call
7438                     savedState = cps;
7439                     ScheduleDelayedEvent(DeferredBookMove, 10);
7440                     return;
7441                 }
7442                 goto FakeBookMove;
7443         }
7444
7445         return;
7446     }
7447
7448     /* Set special modes for chess engines.  Later something general
7449      *  could be added here; for now there is just one kludge feature,
7450      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7451      *  when "xboard" is given as an interactive command.
7452      */
7453     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7454         cps->useSigint = FALSE;
7455         cps->useSigterm = FALSE;
7456     }
7457     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7458       ParseFeatures(message+8, cps);
7459       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7460     }
7461
7462     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7463      * want this, I was asked to put it in, and obliged.
7464      */
7465     if (!strncmp(message, "setboard ", 9)) {
7466         Board initial_position;
7467
7468         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7469
7470         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7471             DisplayError(_("Bad FEN received from engine"), 0);
7472             return ;
7473         } else {
7474            Reset(TRUE, FALSE);
7475            CopyBoard(boards[0], initial_position);
7476            initialRulePlies = FENrulePlies;
7477            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7478            else gameMode = MachinePlaysBlack;
7479            DrawPosition(FALSE, boards[currentMove]);
7480         }
7481         return;
7482     }
7483
7484     /*
7485      * Look for communication commands
7486      */
7487     if (!strncmp(message, "telluser ", 9)) {
7488         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7489         DisplayNote(message + 9);
7490         return;
7491     }
7492     if (!strncmp(message, "tellusererror ", 14)) {
7493         cps->userError = 1;
7494         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7495         DisplayError(message + 14, 0);
7496         return;
7497     }
7498     if (!strncmp(message, "tellopponent ", 13)) {
7499       if (appData.icsActive) {
7500         if (loggedOn) {
7501           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7502           SendToICS(buf1);
7503         }
7504       } else {
7505         DisplayNote(message + 13);
7506       }
7507       return;
7508     }
7509     if (!strncmp(message, "tellothers ", 11)) {
7510       if (appData.icsActive) {
7511         if (loggedOn) {
7512           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7513           SendToICS(buf1);
7514         }
7515       }
7516       return;
7517     }
7518     if (!strncmp(message, "tellall ", 8)) {
7519       if (appData.icsActive) {
7520         if (loggedOn) {
7521           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7522           SendToICS(buf1);
7523         }
7524       } else {
7525         DisplayNote(message + 8);
7526       }
7527       return;
7528     }
7529     if (strncmp(message, "warning", 7) == 0) {
7530         /* Undocumented feature, use tellusererror in new code */
7531         DisplayError(message, 0);
7532         return;
7533     }
7534     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7535         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7536         strcat(realname, " query");
7537         AskQuestion(realname, buf2, buf1, cps->pr);
7538         return;
7539     }
7540     /* Commands from the engine directly to ICS.  We don't allow these to be
7541      *  sent until we are logged on. Crafty kibitzes have been known to
7542      *  interfere with the login process.
7543      */
7544     if (loggedOn) {
7545         if (!strncmp(message, "tellics ", 8)) {
7546             SendToICS(message + 8);
7547             SendToICS("\n");
7548             return;
7549         }
7550         if (!strncmp(message, "tellicsnoalias ", 15)) {
7551             SendToICS(ics_prefix);
7552             SendToICS(message + 15);
7553             SendToICS("\n");
7554             return;
7555         }
7556         /* The following are for backward compatibility only */
7557         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7558             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7559             SendToICS(ics_prefix);
7560             SendToICS(message);
7561             SendToICS("\n");
7562             return;
7563         }
7564     }
7565     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7566         return;
7567     }
7568     /*
7569      * If the move is illegal, cancel it and redraw the board.
7570      * Also deal with other error cases.  Matching is rather loose
7571      * here to accommodate engines written before the spec.
7572      */
7573     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7574         strncmp(message, "Error", 5) == 0) {
7575         if (StrStr(message, "name") ||
7576             StrStr(message, "rating") || StrStr(message, "?") ||
7577             StrStr(message, "result") || StrStr(message, "board") ||
7578             StrStr(message, "bk") || StrStr(message, "computer") ||
7579             StrStr(message, "variant") || StrStr(message, "hint") ||
7580             StrStr(message, "random") || StrStr(message, "depth") ||
7581             StrStr(message, "accepted")) {
7582             return;
7583         }
7584         if (StrStr(message, "protover")) {
7585           /* Program is responding to input, so it's apparently done
7586              initializing, and this error message indicates it is
7587              protocol version 1.  So we don't need to wait any longer
7588              for it to initialize and send feature commands. */
7589           FeatureDone(cps, 1);
7590           cps->protocolVersion = 1;
7591           return;
7592         }
7593         cps->maybeThinking = FALSE;
7594
7595         if (StrStr(message, "draw")) {
7596             /* Program doesn't have "draw" command */
7597             cps->sendDrawOffers = 0;
7598             return;
7599         }
7600         if (cps->sendTime != 1 &&
7601             (StrStr(message, "time") || StrStr(message, "otim"))) {
7602           /* Program apparently doesn't have "time" or "otim" command */
7603           cps->sendTime = 0;
7604           return;
7605         }
7606         if (StrStr(message, "analyze")) {
7607             cps->analysisSupport = FALSE;
7608             cps->analyzing = FALSE;
7609             Reset(FALSE, TRUE);
7610             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7611             DisplayError(buf2, 0);
7612             return;
7613         }
7614         if (StrStr(message, "(no matching move)st")) {
7615           /* Special kludge for GNU Chess 4 only */
7616           cps->stKludge = TRUE;
7617           SendTimeControl(cps, movesPerSession, timeControl,
7618                           timeIncrement, appData.searchDepth,
7619                           searchTime);
7620           return;
7621         }
7622         if (StrStr(message, "(no matching move)sd")) {
7623           /* Special kludge for GNU Chess 4 only */
7624           cps->sdKludge = TRUE;
7625           SendTimeControl(cps, movesPerSession, timeControl,
7626                           timeIncrement, appData.searchDepth,
7627                           searchTime);
7628           return;
7629         }
7630         if (!StrStr(message, "llegal")) {
7631             return;
7632         }
7633         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7634             gameMode == IcsIdle) return;
7635         if (forwardMostMove <= backwardMostMove) return;
7636         if (pausing) PauseEvent();
7637       if(appData.forceIllegal) {
7638             // [HGM] illegal: machine refused move; force position after move into it
7639           SendToProgram("force\n", cps);
7640           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7641                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7642                 // when black is to move, while there might be nothing on a2 or black
7643                 // might already have the move. So send the board as if white has the move.
7644                 // But first we must change the stm of the engine, as it refused the last move
7645                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7646                 if(WhiteOnMove(forwardMostMove)) {
7647                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7648                     SendBoard(cps, forwardMostMove); // kludgeless board
7649                 } else {
7650                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7651                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7652                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7653                 }
7654           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7655             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7656                  gameMode == TwoMachinesPlay)
7657               SendToProgram("go\n", cps);
7658             return;
7659       } else
7660         if (gameMode == PlayFromGameFile) {
7661             /* Stop reading this game file */
7662             gameMode = EditGame;
7663             ModeHighlight();
7664         }
7665         currentMove = forwardMostMove-1;
7666         DisplayMove(currentMove-1); /* before DisplayMoveError */
7667         SwitchClocks(forwardMostMove-1); // [HGM] race
7668         DisplayBothClocks();
7669         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7670                 parseList[currentMove], cps->which);
7671         DisplayMoveError(buf1);
7672         DrawPosition(FALSE, boards[currentMove]);
7673
7674         /* [HGM] illegal-move claim should forfeit game when Xboard */
7675         /* only passes fully legal moves                            */
7676         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7677             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7678                                 "False illegal-move claim", GE_XBOARD );
7679         }
7680         return;
7681     }
7682     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7683         /* Program has a broken "time" command that
7684            outputs a string not ending in newline.
7685            Don't use it. */
7686         cps->sendTime = 0;
7687     }
7688
7689     /*
7690      * If chess program startup fails, exit with an error message.
7691      * Attempts to recover here are futile.
7692      */
7693     if ((StrStr(message, "unknown host") != NULL)
7694         || (StrStr(message, "No remote directory") != NULL)
7695         || (StrStr(message, "not found") != NULL)
7696         || (StrStr(message, "No such file") != NULL)
7697         || (StrStr(message, "can't alloc") != NULL)
7698         || (StrStr(message, "Permission denied") != NULL)) {
7699
7700         cps->maybeThinking = FALSE;
7701         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7702                 cps->which, cps->program, cps->host, message);
7703         RemoveInputSource(cps->isr);
7704         DisplayFatalError(buf1, 0, 1);
7705         return;
7706     }
7707
7708     /*
7709      * Look for hint output
7710      */
7711     if (sscanf(message, "Hint: %s", buf1) == 1) {
7712         if (cps == &first && hintRequested) {
7713             hintRequested = FALSE;
7714             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7715                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7716                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7717                                     PosFlags(forwardMostMove),
7718                                     fromY, fromX, toY, toX, promoChar, buf1);
7719                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7720                 DisplayInformation(buf2);
7721             } else {
7722                 /* Hint move could not be parsed!? */
7723               snprintf(buf2, sizeof(buf2),
7724                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7725                         buf1, cps->which);
7726                 DisplayError(buf2, 0);
7727             }
7728         } else {
7729           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7730         }
7731         return;
7732     }
7733
7734     /*
7735      * Ignore other messages if game is not in progress
7736      */
7737     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7738         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7739
7740     /*
7741      * look for win, lose, draw, or draw offer
7742      */
7743     if (strncmp(message, "1-0", 3) == 0) {
7744         char *p, *q, *r = "";
7745         p = strchr(message, '{');
7746         if (p) {
7747             q = strchr(p, '}');
7748             if (q) {
7749                 *q = NULLCHAR;
7750                 r = p + 1;
7751             }
7752         }
7753         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7754         return;
7755     } else if (strncmp(message, "0-1", 3) == 0) {
7756         char *p, *q, *r = "";
7757         p = strchr(message, '{');
7758         if (p) {
7759             q = strchr(p, '}');
7760             if (q) {
7761                 *q = NULLCHAR;
7762                 r = p + 1;
7763             }
7764         }
7765         /* Kludge for Arasan 4.1 bug */
7766         if (strcmp(r, "Black resigns") == 0) {
7767             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7768             return;
7769         }
7770         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7771         return;
7772     } else if (strncmp(message, "1/2", 3) == 0) {
7773         char *p, *q, *r = "";
7774         p = strchr(message, '{');
7775         if (p) {
7776             q = strchr(p, '}');
7777             if (q) {
7778                 *q = NULLCHAR;
7779                 r = p + 1;
7780             }
7781         }
7782
7783         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7784         return;
7785
7786     } else if (strncmp(message, "White resign", 12) == 0) {
7787         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7788         return;
7789     } else if (strncmp(message, "Black resign", 12) == 0) {
7790         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7791         return;
7792     } else if (strncmp(message, "White matches", 13) == 0 ||
7793                strncmp(message, "Black matches", 13) == 0   ) {
7794         /* [HGM] ignore GNUShogi noises */
7795         return;
7796     } else if (strncmp(message, "White", 5) == 0 &&
7797                message[5] != '(' &&
7798                StrStr(message, "Black") == NULL) {
7799         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7800         return;
7801     } else if (strncmp(message, "Black", 5) == 0 &&
7802                message[5] != '(') {
7803         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7804         return;
7805     } else if (strcmp(message, "resign") == 0 ||
7806                strcmp(message, "computer resigns") == 0) {
7807         switch (gameMode) {
7808           case MachinePlaysBlack:
7809           case IcsPlayingBlack:
7810             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7811             break;
7812           case MachinePlaysWhite:
7813           case IcsPlayingWhite:
7814             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7815             break;
7816           case TwoMachinesPlay:
7817             if (cps->twoMachinesColor[0] == 'w')
7818               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7819             else
7820               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7821             break;
7822           default:
7823             /* can't happen */
7824             break;
7825         }
7826         return;
7827     } else if (strncmp(message, "opponent mates", 14) == 0) {
7828         switch (gameMode) {
7829           case MachinePlaysBlack:
7830           case IcsPlayingBlack:
7831             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7832             break;
7833           case MachinePlaysWhite:
7834           case IcsPlayingWhite:
7835             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7836             break;
7837           case TwoMachinesPlay:
7838             if (cps->twoMachinesColor[0] == 'w')
7839               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7840             else
7841               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7842             break;
7843           default:
7844             /* can't happen */
7845             break;
7846         }
7847         return;
7848     } else if (strncmp(message, "computer mates", 14) == 0) {
7849         switch (gameMode) {
7850           case MachinePlaysBlack:
7851           case IcsPlayingBlack:
7852             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7853             break;
7854           case MachinePlaysWhite:
7855           case IcsPlayingWhite:
7856             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7857             break;
7858           case TwoMachinesPlay:
7859             if (cps->twoMachinesColor[0] == 'w')
7860               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7861             else
7862               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7863             break;
7864           default:
7865             /* can't happen */
7866             break;
7867         }
7868         return;
7869     } else if (strncmp(message, "checkmate", 9) == 0) {
7870         if (WhiteOnMove(forwardMostMove)) {
7871             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7872         } else {
7873             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7874         }
7875         return;
7876     } else if (strstr(message, "Draw") != NULL ||
7877                strstr(message, "game is a draw") != NULL) {
7878         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7879         return;
7880     } else if (strstr(message, "offer") != NULL &&
7881                strstr(message, "draw") != NULL) {
7882 #if ZIPPY
7883         if (appData.zippyPlay && first.initDone) {
7884             /* Relay offer to ICS */
7885             SendToICS(ics_prefix);
7886             SendToICS("draw\n");
7887         }
7888 #endif
7889         cps->offeredDraw = 2; /* valid until this engine moves twice */
7890         if (gameMode == TwoMachinesPlay) {
7891             if (cps->other->offeredDraw) {
7892                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7893             /* [HGM] in two-machine mode we delay relaying draw offer      */
7894             /* until after we also have move, to see if it is really claim */
7895             }
7896         } else if (gameMode == MachinePlaysWhite ||
7897                    gameMode == MachinePlaysBlack) {
7898           if (userOfferedDraw) {
7899             DisplayInformation(_("Machine accepts your draw offer"));
7900             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7901           } else {
7902             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7903           }
7904         }
7905     }
7906
7907
7908     /*
7909      * Look for thinking output
7910      */
7911     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7912           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7913                                 ) {
7914         int plylev, mvleft, mvtot, curscore, time;
7915         char mvname[MOVE_LEN];
7916         u64 nodes; // [DM]
7917         char plyext;
7918         int ignore = FALSE;
7919         int prefixHint = FALSE;
7920         mvname[0] = NULLCHAR;
7921
7922         switch (gameMode) {
7923           case MachinePlaysBlack:
7924           case IcsPlayingBlack:
7925             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7926             break;
7927           case MachinePlaysWhite:
7928           case IcsPlayingWhite:
7929             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7930             break;
7931           case AnalyzeMode:
7932           case AnalyzeFile:
7933             break;
7934           case IcsObserving: /* [DM] icsEngineAnalyze */
7935             if (!appData.icsEngineAnalyze) ignore = TRUE;
7936             break;
7937           case TwoMachinesPlay:
7938             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7939                 ignore = TRUE;
7940             }
7941             break;
7942           default:
7943             ignore = TRUE;
7944             break;
7945         }
7946
7947         if (!ignore) {
7948             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7949             buf1[0] = NULLCHAR;
7950             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7951                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7952
7953                 if (plyext != ' ' && plyext != '\t') {
7954                     time *= 100;
7955                 }
7956
7957                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7958                 if( cps->scoreIsAbsolute &&
7959                     ( gameMode == MachinePlaysBlack ||
7960                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7961                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7962                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7963                      !WhiteOnMove(currentMove)
7964                     ) )
7965                 {
7966                     curscore = -curscore;
7967                 }
7968
7969
7970                 tempStats.depth = plylev;
7971                 tempStats.nodes = nodes;
7972                 tempStats.time = time;
7973                 tempStats.score = curscore;
7974                 tempStats.got_only_move = 0;
7975
7976                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7977                         int ticklen;
7978
7979                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7980                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7981                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7982                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7983                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7984                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7985                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7986                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7987                 }
7988
7989                 /* Buffer overflow protection */
7990                 if (buf1[0] != NULLCHAR) {
7991                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7992                         && appData.debugMode) {
7993                         fprintf(debugFP,
7994                                 "PV is too long; using the first %u bytes.\n",
7995                                 (unsigned) sizeof(tempStats.movelist) - 1);
7996                     }
7997
7998                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
7999                 } else {
8000                     sprintf(tempStats.movelist, " no PV\n");
8001                 }
8002
8003                 if (tempStats.seen_stat) {
8004                     tempStats.ok_to_send = 1;
8005                 }
8006
8007                 if (strchr(tempStats.movelist, '(') != NULL) {
8008                     tempStats.line_is_book = 1;
8009                     tempStats.nr_moves = 0;
8010                     tempStats.moves_left = 0;
8011                 } else {
8012                     tempStats.line_is_book = 0;
8013                 }
8014
8015                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8016                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8017
8018                 SendProgramStatsToFrontend( cps, &tempStats );
8019
8020                 /*
8021                     [AS] Protect the thinkOutput buffer from overflow... this
8022                     is only useful if buf1 hasn't overflowed first!
8023                 */
8024                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8025                          plylev,
8026                          (gameMode == TwoMachinesPlay ?
8027                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8028                          ((double) curscore) / 100.0,
8029                          prefixHint ? lastHint : "",
8030                          prefixHint ? " " : "" );
8031
8032                 if( buf1[0] != NULLCHAR ) {
8033                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8034
8035                     if( strlen(buf1) > max_len ) {
8036                         if( appData.debugMode) {
8037                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8038                         }
8039                         buf1[max_len+1] = '\0';
8040                     }
8041
8042                     strcat( thinkOutput, buf1 );
8043                 }
8044
8045                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8046                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8047                     DisplayMove(currentMove - 1);
8048                 }
8049                 return;
8050
8051             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8052                 /* crafty (9.25+) says "(only move) <move>"
8053                  * if there is only 1 legal move
8054                  */
8055                 sscanf(p, "(only move) %s", buf1);
8056                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8057                 sprintf(programStats.movelist, "%s (only move)", buf1);
8058                 programStats.depth = 1;
8059                 programStats.nr_moves = 1;
8060                 programStats.moves_left = 1;
8061                 programStats.nodes = 1;
8062                 programStats.time = 1;
8063                 programStats.got_only_move = 1;
8064
8065                 /* Not really, but we also use this member to
8066                    mean "line isn't going to change" (Crafty
8067                    isn't searching, so stats won't change) */
8068                 programStats.line_is_book = 1;
8069
8070                 SendProgramStatsToFrontend( cps, &programStats );
8071
8072                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8073                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8074                     DisplayMove(currentMove - 1);
8075                 }
8076                 return;
8077             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8078                               &time, &nodes, &plylev, &mvleft,
8079                               &mvtot, mvname) >= 5) {
8080                 /* The stat01: line is from Crafty (9.29+) in response
8081                    to the "." command */
8082                 programStats.seen_stat = 1;
8083                 cps->maybeThinking = TRUE;
8084
8085                 if (programStats.got_only_move || !appData.periodicUpdates)
8086                   return;
8087
8088                 programStats.depth = plylev;
8089                 programStats.time = time;
8090                 programStats.nodes = nodes;
8091                 programStats.moves_left = mvleft;
8092                 programStats.nr_moves = mvtot;
8093                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8094                 programStats.ok_to_send = 1;
8095                 programStats.movelist[0] = '\0';
8096
8097                 SendProgramStatsToFrontend( cps, &programStats );
8098
8099                 return;
8100
8101             } else if (strncmp(message,"++",2) == 0) {
8102                 /* Crafty 9.29+ outputs this */
8103                 programStats.got_fail = 2;
8104                 return;
8105
8106             } else if (strncmp(message,"--",2) == 0) {
8107                 /* Crafty 9.29+ outputs this */
8108                 programStats.got_fail = 1;
8109                 return;
8110
8111             } else if (thinkOutput[0] != NULLCHAR &&
8112                        strncmp(message, "    ", 4) == 0) {
8113                 unsigned message_len;
8114
8115                 p = message;
8116                 while (*p && *p == ' ') p++;
8117
8118                 message_len = strlen( p );
8119
8120                 /* [AS] Avoid buffer overflow */
8121                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8122                     strcat(thinkOutput, " ");
8123                     strcat(thinkOutput, p);
8124                 }
8125
8126                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8127                     strcat(programStats.movelist, " ");
8128                     strcat(programStats.movelist, p);
8129                 }
8130
8131                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8132                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8133                     DisplayMove(currentMove - 1);
8134                 }
8135                 return;
8136             }
8137         }
8138         else {
8139             buf1[0] = NULLCHAR;
8140
8141             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8142                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8143             {
8144                 ChessProgramStats cpstats;
8145
8146                 if (plyext != ' ' && plyext != '\t') {
8147                     time *= 100;
8148                 }
8149
8150                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8151                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8152                     curscore = -curscore;
8153                 }
8154
8155                 cpstats.depth = plylev;
8156                 cpstats.nodes = nodes;
8157                 cpstats.time = time;
8158                 cpstats.score = curscore;
8159                 cpstats.got_only_move = 0;
8160                 cpstats.movelist[0] = '\0';
8161
8162                 if (buf1[0] != NULLCHAR) {
8163                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8164                 }
8165
8166                 cpstats.ok_to_send = 0;
8167                 cpstats.line_is_book = 0;
8168                 cpstats.nr_moves = 0;
8169                 cpstats.moves_left = 0;
8170
8171                 SendProgramStatsToFrontend( cps, &cpstats );
8172             }
8173         }
8174     }
8175 }
8176
8177
8178 /* Parse a game score from the character string "game", and
8179    record it as the history of the current game.  The game
8180    score is NOT assumed to start from the standard position.
8181    The display is not updated in any way.
8182    */
8183 void
8184 ParseGameHistory(game)
8185      char *game;
8186 {
8187     ChessMove moveType;
8188     int fromX, fromY, toX, toY, boardIndex;
8189     char promoChar;
8190     char *p, *q;
8191     char buf[MSG_SIZ];
8192
8193     if (appData.debugMode)
8194       fprintf(debugFP, "Parsing game history: %s\n", game);
8195
8196     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8197     gameInfo.site = StrSave(appData.icsHost);
8198     gameInfo.date = PGNDate();
8199     gameInfo.round = StrSave("-");
8200
8201     /* Parse out names of players */
8202     while (*game == ' ') game++;
8203     p = buf;
8204     while (*game != ' ') *p++ = *game++;
8205     *p = NULLCHAR;
8206     gameInfo.white = StrSave(buf);
8207     while (*game == ' ') game++;
8208     p = buf;
8209     while (*game != ' ' && *game != '\n') *p++ = *game++;
8210     *p = NULLCHAR;
8211     gameInfo.black = StrSave(buf);
8212
8213     /* Parse moves */
8214     boardIndex = blackPlaysFirst ? 1 : 0;
8215     yynewstr(game);
8216     for (;;) {
8217         yyboardindex = boardIndex;
8218         moveType = (ChessMove) yylex();
8219         switch (moveType) {
8220           case IllegalMove:             /* maybe suicide chess, etc. */
8221   if (appData.debugMode) {
8222     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8223     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8224     setbuf(debugFP, NULL);
8225   }
8226           case WhitePromotion:
8227           case BlackPromotion:
8228           case WhiteNonPromotion:
8229           case BlackNonPromotion:
8230           case NormalMove:
8231           case WhiteCapturesEnPassant:
8232           case BlackCapturesEnPassant:
8233           case WhiteKingSideCastle:
8234           case WhiteQueenSideCastle:
8235           case BlackKingSideCastle:
8236           case BlackQueenSideCastle:
8237           case WhiteKingSideCastleWild:
8238           case WhiteQueenSideCastleWild:
8239           case BlackKingSideCastleWild:
8240           case BlackQueenSideCastleWild:
8241           /* PUSH Fabien */
8242           case WhiteHSideCastleFR:
8243           case WhiteASideCastleFR:
8244           case BlackHSideCastleFR:
8245           case BlackASideCastleFR:
8246           /* POP Fabien */
8247             fromX = currentMoveString[0] - AAA;
8248             fromY = currentMoveString[1] - ONE;
8249             toX = currentMoveString[2] - AAA;
8250             toY = currentMoveString[3] - ONE;
8251             promoChar = currentMoveString[4];
8252             break;
8253           case WhiteDrop:
8254           case BlackDrop:
8255             fromX = moveType == WhiteDrop ?
8256               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8257             (int) CharToPiece(ToLower(currentMoveString[0]));
8258             fromY = DROP_RANK;
8259             toX = currentMoveString[2] - AAA;
8260             toY = currentMoveString[3] - ONE;
8261             promoChar = NULLCHAR;
8262             break;
8263           case AmbiguousMove:
8264             /* bug? */
8265             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8266   if (appData.debugMode) {
8267     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8268     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8269     setbuf(debugFP, NULL);
8270   }
8271             DisplayError(buf, 0);
8272             return;
8273           case ImpossibleMove:
8274             /* bug? */
8275             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8276   if (appData.debugMode) {
8277     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8278     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8279     setbuf(debugFP, NULL);
8280   }
8281             DisplayError(buf, 0);
8282             return;
8283           case EndOfFile:
8284             if (boardIndex < backwardMostMove) {
8285                 /* Oops, gap.  How did that happen? */
8286                 DisplayError(_("Gap in move list"), 0);
8287                 return;
8288             }
8289             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8290             if (boardIndex > forwardMostMove) {
8291                 forwardMostMove = boardIndex;
8292             }
8293             return;
8294           case ElapsedTime:
8295             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8296                 strcat(parseList[boardIndex-1], " ");
8297                 strcat(parseList[boardIndex-1], yy_text);
8298             }
8299             continue;
8300           case Comment:
8301           case PGNTag:
8302           case NAG:
8303           default:
8304             /* ignore */
8305             continue;
8306           case WhiteWins:
8307           case BlackWins:
8308           case GameIsDrawn:
8309           case GameUnfinished:
8310             if (gameMode == IcsExamining) {
8311                 if (boardIndex < backwardMostMove) {
8312                     /* Oops, gap.  How did that happen? */
8313                     return;
8314                 }
8315                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8316                 return;
8317             }
8318             gameInfo.result = moveType;
8319             p = strchr(yy_text, '{');
8320             if (p == NULL) p = strchr(yy_text, '(');
8321             if (p == NULL) {
8322                 p = yy_text;
8323                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8324             } else {
8325                 q = strchr(p, *p == '{' ? '}' : ')');
8326                 if (q != NULL) *q = NULLCHAR;
8327                 p++;
8328             }
8329             gameInfo.resultDetails = StrSave(p);
8330             continue;
8331         }
8332         if (boardIndex >= forwardMostMove &&
8333             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8334             backwardMostMove = blackPlaysFirst ? 1 : 0;
8335             return;
8336         }
8337         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8338                                  fromY, fromX, toY, toX, promoChar,
8339                                  parseList[boardIndex]);
8340         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8341         /* currentMoveString is set as a side-effect of yylex */
8342         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8343         strcat(moveList[boardIndex], "\n");
8344         boardIndex++;
8345         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8346         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8347           case MT_NONE:
8348           case MT_STALEMATE:
8349           default:
8350             break;
8351           case MT_CHECK:
8352             if(gameInfo.variant != VariantShogi)
8353                 strcat(parseList[boardIndex - 1], "+");
8354             break;
8355           case MT_CHECKMATE:
8356           case MT_STAINMATE:
8357             strcat(parseList[boardIndex - 1], "#");
8358             break;
8359         }
8360     }
8361 }
8362
8363
8364 /* Apply a move to the given board  */
8365 void
8366 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8367      int fromX, fromY, toX, toY;
8368      int promoChar;
8369      Board board;
8370 {
8371   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8372   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8373
8374     /* [HGM] compute & store e.p. status and castling rights for new position */
8375     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8376
8377       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8378       oldEP = (signed char)board[EP_STATUS];
8379       board[EP_STATUS] = EP_NONE;
8380
8381       if( board[toY][toX] != EmptySquare )
8382            board[EP_STATUS] = EP_CAPTURE;
8383
8384   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8385   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8386        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8387
8388   if (fromY == DROP_RANK) {
8389         /* must be first */
8390         piece = board[toY][toX] = (ChessSquare) fromX;
8391   } else {
8392       int i;
8393
8394       if( board[fromY][fromX] == WhitePawn ) {
8395            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8396                board[EP_STATUS] = EP_PAWN_MOVE;
8397            if( toY-fromY==2) {
8398                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8399                         gameInfo.variant != VariantBerolina || toX < fromX)
8400                       board[EP_STATUS] = toX | berolina;
8401                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8402                         gameInfo.variant != VariantBerolina || toX > fromX)
8403                       board[EP_STATUS] = toX;
8404            }
8405       } else
8406       if( board[fromY][fromX] == BlackPawn ) {
8407            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8408                board[EP_STATUS] = EP_PAWN_MOVE;
8409            if( toY-fromY== -2) {
8410                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8411                         gameInfo.variant != VariantBerolina || toX < fromX)
8412                       board[EP_STATUS] = toX | berolina;
8413                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8414                         gameInfo.variant != VariantBerolina || toX > fromX)
8415                       board[EP_STATUS] = toX;
8416            }
8417        }
8418
8419        for(i=0; i<nrCastlingRights; i++) {
8420            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8421               board[CASTLING][i] == toX   && castlingRank[i] == toY
8422              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8423        }
8424
8425      if (fromX == toX && fromY == toY) return;
8426
8427      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8428      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8429      if(gameInfo.variant == VariantKnightmate)
8430          king += (int) WhiteUnicorn - (int) WhiteKing;
8431
8432     /* Code added by Tord: */
8433     /* FRC castling assumed when king captures friendly rook. */
8434     if (board[fromY][fromX] == WhiteKing &&
8435              board[toY][toX] == WhiteRook) {
8436       board[fromY][fromX] = EmptySquare;
8437       board[toY][toX] = EmptySquare;
8438       if(toX > fromX) {
8439         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8440       } else {
8441         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8442       }
8443     } else if (board[fromY][fromX] == BlackKing &&
8444                board[toY][toX] == BlackRook) {
8445       board[fromY][fromX] = EmptySquare;
8446       board[toY][toX] = EmptySquare;
8447       if(toX > fromX) {
8448         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8449       } else {
8450         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8451       }
8452     /* End of code added by Tord */
8453
8454     } else if (board[fromY][fromX] == king
8455         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8456         && toY == fromY && toX > fromX+1) {
8457         board[fromY][fromX] = EmptySquare;
8458         board[toY][toX] = king;
8459         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8460         board[fromY][BOARD_RGHT-1] = EmptySquare;
8461     } else if (board[fromY][fromX] == king
8462         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8463                && toY == fromY && toX < fromX-1) {
8464         board[fromY][fromX] = EmptySquare;
8465         board[toY][toX] = king;
8466         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8467         board[fromY][BOARD_LEFT] = EmptySquare;
8468     } else if (board[fromY][fromX] == WhitePawn
8469                && toY >= BOARD_HEIGHT-promoRank
8470                && gameInfo.variant != VariantXiangqi
8471                ) {
8472         /* white pawn promotion */
8473         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8474         if (board[toY][toX] == EmptySquare) {
8475             board[toY][toX] = WhiteQueen;
8476         }
8477         if(gameInfo.variant==VariantBughouse ||
8478            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8479             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8480         board[fromY][fromX] = EmptySquare;
8481     } else if ((fromY == BOARD_HEIGHT-4)
8482                && (toX != fromX)
8483                && gameInfo.variant != VariantXiangqi
8484                && gameInfo.variant != VariantBerolina
8485                && (board[fromY][fromX] == WhitePawn)
8486                && (board[toY][toX] == EmptySquare)) {
8487         board[fromY][fromX] = EmptySquare;
8488         board[toY][toX] = WhitePawn;
8489         captured = board[toY - 1][toX];
8490         board[toY - 1][toX] = EmptySquare;
8491     } else if ((fromY == BOARD_HEIGHT-4)
8492                && (toX == fromX)
8493                && gameInfo.variant == VariantBerolina
8494                && (board[fromY][fromX] == WhitePawn)
8495                && (board[toY][toX] == EmptySquare)) {
8496         board[fromY][fromX] = EmptySquare;
8497         board[toY][toX] = WhitePawn;
8498         if(oldEP & EP_BEROLIN_A) {
8499                 captured = board[fromY][fromX-1];
8500                 board[fromY][fromX-1] = EmptySquare;
8501         }else{  captured = board[fromY][fromX+1];
8502                 board[fromY][fromX+1] = EmptySquare;
8503         }
8504     } else if (board[fromY][fromX] == king
8505         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8506                && toY == fromY && toX > fromX+1) {
8507         board[fromY][fromX] = EmptySquare;
8508         board[toY][toX] = king;
8509         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8510         board[fromY][BOARD_RGHT-1] = EmptySquare;
8511     } else if (board[fromY][fromX] == king
8512         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8513                && toY == fromY && toX < fromX-1) {
8514         board[fromY][fromX] = EmptySquare;
8515         board[toY][toX] = king;
8516         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8517         board[fromY][BOARD_LEFT] = EmptySquare;
8518     } else if (fromY == 7 && fromX == 3
8519                && board[fromY][fromX] == BlackKing
8520                && toY == 7 && toX == 5) {
8521         board[fromY][fromX] = EmptySquare;
8522         board[toY][toX] = BlackKing;
8523         board[fromY][7] = EmptySquare;
8524         board[toY][4] = BlackRook;
8525     } else if (fromY == 7 && fromX == 3
8526                && board[fromY][fromX] == BlackKing
8527                && toY == 7 && toX == 1) {
8528         board[fromY][fromX] = EmptySquare;
8529         board[toY][toX] = BlackKing;
8530         board[fromY][0] = EmptySquare;
8531         board[toY][2] = BlackRook;
8532     } else if (board[fromY][fromX] == BlackPawn
8533                && toY < promoRank
8534                && gameInfo.variant != VariantXiangqi
8535                ) {
8536         /* black pawn promotion */
8537         board[toY][toX] = CharToPiece(ToLower(promoChar));
8538         if (board[toY][toX] == EmptySquare) {
8539             board[toY][toX] = BlackQueen;
8540         }
8541         if(gameInfo.variant==VariantBughouse ||
8542            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8543             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8544         board[fromY][fromX] = EmptySquare;
8545     } else if ((fromY == 3)
8546                && (toX != fromX)
8547                && gameInfo.variant != VariantXiangqi
8548                && gameInfo.variant != VariantBerolina
8549                && (board[fromY][fromX] == BlackPawn)
8550                && (board[toY][toX] == EmptySquare)) {
8551         board[fromY][fromX] = EmptySquare;
8552         board[toY][toX] = BlackPawn;
8553         captured = board[toY + 1][toX];
8554         board[toY + 1][toX] = EmptySquare;
8555     } else if ((fromY == 3)
8556                && (toX == fromX)
8557                && gameInfo.variant == VariantBerolina
8558                && (board[fromY][fromX] == BlackPawn)
8559                && (board[toY][toX] == EmptySquare)) {
8560         board[fromY][fromX] = EmptySquare;
8561         board[toY][toX] = BlackPawn;
8562         if(oldEP & EP_BEROLIN_A) {
8563                 captured = board[fromY][fromX-1];
8564                 board[fromY][fromX-1] = EmptySquare;
8565         }else{  captured = board[fromY][fromX+1];
8566                 board[fromY][fromX+1] = EmptySquare;
8567         }
8568     } else {
8569         board[toY][toX] = board[fromY][fromX];
8570         board[fromY][fromX] = EmptySquare;
8571     }
8572   }
8573
8574     if (gameInfo.holdingsWidth != 0) {
8575
8576       /* !!A lot more code needs to be written to support holdings  */
8577       /* [HGM] OK, so I have written it. Holdings are stored in the */
8578       /* penultimate board files, so they are automaticlly stored   */
8579       /* in the game history.                                       */
8580       if (fromY == DROP_RANK) {
8581         /* Delete from holdings, by decreasing count */
8582         /* and erasing image if necessary            */
8583         p = (int) fromX;
8584         if(p < (int) BlackPawn) { /* white drop */
8585              p -= (int)WhitePawn;
8586                  p = PieceToNumber((ChessSquare)p);
8587              if(p >= gameInfo.holdingsSize) p = 0;
8588              if(--board[p][BOARD_WIDTH-2] <= 0)
8589                   board[p][BOARD_WIDTH-1] = EmptySquare;
8590              if((int)board[p][BOARD_WIDTH-2] < 0)
8591                         board[p][BOARD_WIDTH-2] = 0;
8592         } else {                  /* black drop */
8593              p -= (int)BlackPawn;
8594                  p = PieceToNumber((ChessSquare)p);
8595              if(p >= gameInfo.holdingsSize) p = 0;
8596              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8597                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8598              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8599                         board[BOARD_HEIGHT-1-p][1] = 0;
8600         }
8601       }
8602       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8603           && gameInfo.variant != VariantBughouse        ) {
8604         /* [HGM] holdings: Add to holdings, if holdings exist */
8605         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8606                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8607                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8608         }
8609         p = (int) captured;
8610         if (p >= (int) BlackPawn) {
8611           p -= (int)BlackPawn;
8612           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8613                   /* in Shogi restore piece to its original  first */
8614                   captured = (ChessSquare) (DEMOTED captured);
8615                   p = DEMOTED p;
8616           }
8617           p = PieceToNumber((ChessSquare)p);
8618           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8619           board[p][BOARD_WIDTH-2]++;
8620           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8621         } else {
8622           p -= (int)WhitePawn;
8623           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8624                   captured = (ChessSquare) (DEMOTED captured);
8625                   p = DEMOTED p;
8626           }
8627           p = PieceToNumber((ChessSquare)p);
8628           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8629           board[BOARD_HEIGHT-1-p][1]++;
8630           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8631         }
8632       }
8633     } else if (gameInfo.variant == VariantAtomic) {
8634       if (captured != EmptySquare) {
8635         int y, x;
8636         for (y = toY-1; y <= toY+1; y++) {
8637           for (x = toX-1; x <= toX+1; x++) {
8638             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8639                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8640               board[y][x] = EmptySquare;
8641             }
8642           }
8643         }
8644         board[toY][toX] = EmptySquare;
8645       }
8646     }
8647     if(promoChar == '+') {
8648         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8649         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8650     } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
8651         board[toY][toX] = CharToPiece(promoChar);
8652     }
8653     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8654                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8655         // [HGM] superchess: take promotion piece out of holdings
8656         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8657         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8658             if(!--board[k][BOARD_WIDTH-2])
8659                 board[k][BOARD_WIDTH-1] = EmptySquare;
8660         } else {
8661             if(!--board[BOARD_HEIGHT-1-k][1])
8662                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8663         }
8664     }
8665
8666 }
8667
8668 /* Updates forwardMostMove */
8669 void
8670 MakeMove(fromX, fromY, toX, toY, promoChar)
8671      int fromX, fromY, toX, toY;
8672      int promoChar;
8673 {
8674 //    forwardMostMove++; // [HGM] bare: moved downstream
8675
8676     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8677         int timeLeft; static int lastLoadFlag=0; int king, piece;
8678         piece = boards[forwardMostMove][fromY][fromX];
8679         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8680         if(gameInfo.variant == VariantKnightmate)
8681             king += (int) WhiteUnicorn - (int) WhiteKing;
8682         if(forwardMostMove == 0) {
8683             if(blackPlaysFirst)
8684                 fprintf(serverMoves, "%s;", second.tidy);
8685             fprintf(serverMoves, "%s;", first.tidy);
8686             if(!blackPlaysFirst)
8687                 fprintf(serverMoves, "%s;", second.tidy);
8688         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8689         lastLoadFlag = loadFlag;
8690         // print base move
8691         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8692         // print castling suffix
8693         if( toY == fromY && piece == king ) {
8694             if(toX-fromX > 1)
8695                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8696             if(fromX-toX >1)
8697                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8698         }
8699         // e.p. suffix
8700         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8701              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8702              boards[forwardMostMove][toY][toX] == EmptySquare
8703              && fromX != toX && fromY != toY)
8704                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8705         // promotion suffix
8706         if(promoChar != NULLCHAR)
8707                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8708         if(!loadFlag) {
8709             fprintf(serverMoves, "/%d/%d",
8710                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8711             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8712             else                      timeLeft = blackTimeRemaining/1000;
8713             fprintf(serverMoves, "/%d", timeLeft);
8714         }
8715         fflush(serverMoves);
8716     }
8717
8718     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8719       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8720                         0, 1);
8721       return;
8722     }
8723     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8724     if (commentList[forwardMostMove+1] != NULL) {
8725         free(commentList[forwardMostMove+1]);
8726         commentList[forwardMostMove+1] = NULL;
8727     }
8728     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8729     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8730     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8731     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8732     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8733     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8734     gameInfo.result = GameUnfinished;
8735     if (gameInfo.resultDetails != NULL) {
8736         free(gameInfo.resultDetails);
8737         gameInfo.resultDetails = NULL;
8738     }
8739     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8740                               moveList[forwardMostMove - 1]);
8741     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8742                              PosFlags(forwardMostMove - 1),
8743                              fromY, fromX, toY, toX, promoChar,
8744                              parseList[forwardMostMove - 1]);
8745     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8746       case MT_NONE:
8747       case MT_STALEMATE:
8748       default:
8749         break;
8750       case MT_CHECK:
8751         if(gameInfo.variant != VariantShogi)
8752             strcat(parseList[forwardMostMove - 1], "+");
8753         break;
8754       case MT_CHECKMATE:
8755       case MT_STAINMATE:
8756         strcat(parseList[forwardMostMove - 1], "#");
8757         break;
8758     }
8759     if (appData.debugMode) {
8760         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8761     }
8762
8763 }
8764
8765 /* Updates currentMove if not pausing */
8766 void
8767 ShowMove(fromX, fromY, toX, toY)
8768 {
8769     int instant = (gameMode == PlayFromGameFile) ?
8770         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8771     if(appData.noGUI) return;
8772     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8773         if (!instant) {
8774             if (forwardMostMove == currentMove + 1) {
8775                 AnimateMove(boards[forwardMostMove - 1],
8776                             fromX, fromY, toX, toY);
8777             }
8778             if (appData.highlightLastMove) {
8779                 SetHighlights(fromX, fromY, toX, toY);
8780             }
8781         }
8782         currentMove = forwardMostMove;
8783     }
8784
8785     if (instant) return;
8786
8787     DisplayMove(currentMove - 1);
8788     DrawPosition(FALSE, boards[currentMove]);
8789     DisplayBothClocks();
8790     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8791 }
8792
8793 void SendEgtPath(ChessProgramState *cps)
8794 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8795         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8796
8797         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8798
8799         while(*p) {
8800             char c, *q = name+1, *r, *s;
8801
8802             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8803             while(*p && *p != ',') *q++ = *p++;
8804             *q++ = ':'; *q = 0;
8805             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8806                 strcmp(name, ",nalimov:") == 0 ) {
8807                 // take nalimov path from the menu-changeable option first, if it is defined
8808               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8809                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8810             } else
8811             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8812                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8813                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8814                 s = r = StrStr(s, ":") + 1; // beginning of path info
8815                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8816                 c = *r; *r = 0;             // temporarily null-terminate path info
8817                     *--q = 0;               // strip of trailig ':' from name
8818                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8819                 *r = c;
8820                 SendToProgram(buf,cps);     // send egtbpath command for this format
8821             }
8822             if(*p == ',') p++; // read away comma to position for next format name
8823         }
8824 }
8825
8826 void
8827 InitChessProgram(cps, setup)
8828      ChessProgramState *cps;
8829      int setup; /* [HGM] needed to setup FRC opening position */
8830 {
8831     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8832     if (appData.noChessProgram) return;
8833     hintRequested = FALSE;
8834     bookRequested = FALSE;
8835
8836     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8837     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8838     if(cps->memSize) { /* [HGM] memory */
8839       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8840         SendToProgram(buf, cps);
8841     }
8842     SendEgtPath(cps); /* [HGM] EGT */
8843     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8844       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8845         SendToProgram(buf, cps);
8846     }
8847
8848     SendToProgram(cps->initString, cps);
8849     if (gameInfo.variant != VariantNormal &&
8850         gameInfo.variant != VariantLoadable
8851         /* [HGM] also send variant if board size non-standard */
8852         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8853                                             ) {
8854       char *v = VariantName(gameInfo.variant);
8855       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8856         /* [HGM] in protocol 1 we have to assume all variants valid */
8857         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8858         DisplayFatalError(buf, 0, 1);
8859         return;
8860       }
8861
8862       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8863       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8864       if( gameInfo.variant == VariantXiangqi )
8865            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8866       if( gameInfo.variant == VariantShogi )
8867            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8868       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8869            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8870       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8871                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8872            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8873       if( gameInfo.variant == VariantCourier )
8874            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8875       if( gameInfo.variant == VariantSuper )
8876            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8877       if( gameInfo.variant == VariantGreat )
8878            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8879
8880       if(overruled) {
8881         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8882                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8883            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8884            if(StrStr(cps->variants, b) == NULL) {
8885                // specific sized variant not known, check if general sizing allowed
8886                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8887                    if(StrStr(cps->variants, "boardsize") == NULL) {
8888                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8889                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8890                        DisplayFatalError(buf, 0, 1);
8891                        return;
8892                    }
8893                    /* [HGM] here we really should compare with the maximum supported board size */
8894                }
8895            }
8896       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8897       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8898       SendToProgram(buf, cps);
8899     }
8900     currentlyInitializedVariant = gameInfo.variant;
8901
8902     /* [HGM] send opening position in FRC to first engine */
8903     if(setup) {
8904           SendToProgram("force\n", cps);
8905           SendBoard(cps, 0);
8906           /* engine is now in force mode! Set flag to wake it up after first move. */
8907           setboardSpoiledMachineBlack = 1;
8908     }
8909
8910     if (cps->sendICS) {
8911       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8912       SendToProgram(buf, cps);
8913     }
8914     cps->maybeThinking = FALSE;
8915     cps->offeredDraw = 0;
8916     if (!appData.icsActive) {
8917         SendTimeControl(cps, movesPerSession, timeControl,
8918                         timeIncrement, appData.searchDepth,
8919                         searchTime);
8920     }
8921     if (appData.showThinking
8922         // [HGM] thinking: four options require thinking output to be sent
8923         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8924                                 ) {
8925         SendToProgram("post\n", cps);
8926     }
8927     SendToProgram("hard\n", cps);
8928     if (!appData.ponderNextMove) {
8929         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8930            it without being sure what state we are in first.  "hard"
8931            is not a toggle, so that one is OK.
8932          */
8933         SendToProgram("easy\n", cps);
8934     }
8935     if (cps->usePing) {
8936       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8937       SendToProgram(buf, cps);
8938     }
8939     cps->initDone = TRUE;
8940 }
8941
8942
8943 void
8944 StartChessProgram(cps)
8945      ChessProgramState *cps;
8946 {
8947     char buf[MSG_SIZ];
8948     int err;
8949
8950     if (appData.noChessProgram) return;
8951     cps->initDone = FALSE;
8952
8953     if (strcmp(cps->host, "localhost") == 0) {
8954         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8955     } else if (*appData.remoteShell == NULLCHAR) {
8956         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8957     } else {
8958         if (*appData.remoteUser == NULLCHAR) {
8959           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8960                     cps->program);
8961         } else {
8962           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8963                     cps->host, appData.remoteUser, cps->program);
8964         }
8965         err = StartChildProcess(buf, "", &cps->pr);
8966     }
8967
8968     if (err != 0) {
8969       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8970         DisplayFatalError(buf, err, 1);
8971         cps->pr = NoProc;
8972         cps->isr = NULL;
8973         return;
8974     }
8975
8976     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8977     if (cps->protocolVersion > 1) {
8978       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8979       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8980       cps->comboCnt = 0;  //                and values of combo boxes
8981       SendToProgram(buf, cps);
8982     } else {
8983       SendToProgram("xboard\n", cps);
8984     }
8985 }
8986
8987
8988 void
8989 TwoMachinesEventIfReady P((void))
8990 {
8991   if (first.lastPing != first.lastPong) {
8992     DisplayMessage("", _("Waiting for first chess program"));
8993     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8994     return;
8995   }
8996   if (second.lastPing != second.lastPong) {
8997     DisplayMessage("", _("Waiting for second chess program"));
8998     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8999     return;
9000   }
9001   ThawUI();
9002   TwoMachinesEvent();
9003 }
9004
9005 void
9006 NextMatchGame P((void))
9007 {
9008     int index; /* [HGM] autoinc: step load index during match */
9009     Reset(FALSE, TRUE);
9010     if (*appData.loadGameFile != NULLCHAR) {
9011         index = appData.loadGameIndex;
9012         if(index < 0) { // [HGM] autoinc
9013             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9014             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9015         }
9016         LoadGameFromFile(appData.loadGameFile,
9017                          index,
9018                          appData.loadGameFile, FALSE);
9019     } else if (*appData.loadPositionFile != NULLCHAR) {
9020         index = appData.loadPositionIndex;
9021         if(index < 0) { // [HGM] autoinc
9022             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9023             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9024         }
9025         LoadPositionFromFile(appData.loadPositionFile,
9026                              index,
9027                              appData.loadPositionFile);
9028     }
9029     TwoMachinesEventIfReady();
9030 }
9031
9032 void UserAdjudicationEvent( int result )
9033 {
9034     ChessMove gameResult = GameIsDrawn;
9035
9036     if( result > 0 ) {
9037         gameResult = WhiteWins;
9038     }
9039     else if( result < 0 ) {
9040         gameResult = BlackWins;
9041     }
9042
9043     if( gameMode == TwoMachinesPlay ) {
9044         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9045     }
9046 }
9047
9048
9049 // [HGM] save: calculate checksum of game to make games easily identifiable
9050 int StringCheckSum(char *s)
9051 {
9052         int i = 0;
9053         if(s==NULL) return 0;
9054         while(*s) i = i*259 + *s++;
9055         return i;
9056 }
9057
9058 int GameCheckSum()
9059 {
9060         int i, sum=0;
9061         for(i=backwardMostMove; i<forwardMostMove; i++) {
9062                 sum += pvInfoList[i].depth;
9063                 sum += StringCheckSum(parseList[i]);
9064                 sum += StringCheckSum(commentList[i]);
9065                 sum *= 261;
9066         }
9067         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9068         return sum + StringCheckSum(commentList[i]);
9069 } // end of save patch
9070
9071 void
9072 GameEnds(result, resultDetails, whosays)
9073      ChessMove result;
9074      char *resultDetails;
9075      int whosays;
9076 {
9077     GameMode nextGameMode;
9078     int isIcsGame;
9079     char buf[MSG_SIZ], popupRequested = 0;
9080
9081     if(endingGame) return; /* [HGM] crash: forbid recursion */
9082     endingGame = 1;
9083     if(twoBoards) { // [HGM] dual: switch back to one board
9084         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9085         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9086     }
9087     if (appData.debugMode) {
9088       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9089               result, resultDetails ? resultDetails : "(null)", whosays);
9090     }
9091
9092     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9093
9094     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9095         /* If we are playing on ICS, the server decides when the
9096            game is over, but the engine can offer to draw, claim
9097            a draw, or resign.
9098          */
9099 #if ZIPPY
9100         if (appData.zippyPlay && first.initDone) {
9101             if (result == GameIsDrawn) {
9102                 /* In case draw still needs to be claimed */
9103                 SendToICS(ics_prefix);
9104                 SendToICS("draw\n");
9105             } else if (StrCaseStr(resultDetails, "resign")) {
9106                 SendToICS(ics_prefix);
9107                 SendToICS("resign\n");
9108             }
9109         }
9110 #endif
9111         endingGame = 0; /* [HGM] crash */
9112         return;
9113     }
9114
9115     /* If we're loading the game from a file, stop */
9116     if (whosays == GE_FILE) {
9117       (void) StopLoadGameTimer();
9118       gameFileFP = NULL;
9119     }
9120
9121     /* Cancel draw offers */
9122     first.offeredDraw = second.offeredDraw = 0;
9123
9124     /* If this is an ICS game, only ICS can really say it's done;
9125        if not, anyone can. */
9126     isIcsGame = (gameMode == IcsPlayingWhite ||
9127                  gameMode == IcsPlayingBlack ||
9128                  gameMode == IcsObserving    ||
9129                  gameMode == IcsExamining);
9130
9131     if (!isIcsGame || whosays == GE_ICS) {
9132         /* OK -- not an ICS game, or ICS said it was done */
9133         StopClocks();
9134         if (!isIcsGame && !appData.noChessProgram)
9135           SetUserThinkingEnables();
9136
9137         /* [HGM] if a machine claims the game end we verify this claim */
9138         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9139             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9140                 char claimer;
9141                 ChessMove trueResult = (ChessMove) -1;
9142
9143                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9144                                             first.twoMachinesColor[0] :
9145                                             second.twoMachinesColor[0] ;
9146
9147                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9148                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9149                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9150                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9151                 } else
9152                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9153                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9154                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9155                 } else
9156                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9157                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9158                 }
9159
9160                 // now verify win claims, but not in drop games, as we don't understand those yet
9161                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9162                                                  || gameInfo.variant == VariantGreat) &&
9163                     (result == WhiteWins && claimer == 'w' ||
9164                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9165                       if (appData.debugMode) {
9166                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9167                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9168                       }
9169                       if(result != trueResult) {
9170                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9171                               result = claimer == 'w' ? BlackWins : WhiteWins;
9172                               resultDetails = buf;
9173                       }
9174                 } else
9175                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9176                     && (forwardMostMove <= backwardMostMove ||
9177                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9178                         (claimer=='b')==(forwardMostMove&1))
9179                                                                                   ) {
9180                       /* [HGM] verify: draws that were not flagged are false claims */
9181                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9182                       result = claimer == 'w' ? BlackWins : WhiteWins;
9183                       resultDetails = buf;
9184                 }
9185                 /* (Claiming a loss is accepted no questions asked!) */
9186             }
9187             /* [HGM] bare: don't allow bare King to win */
9188             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9189                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9190                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9191                && result != GameIsDrawn)
9192             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9193                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9194                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9195                         if(p >= 0 && p <= (int)WhiteKing) k++;
9196                 }
9197                 if (appData.debugMode) {
9198                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9199                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9200                 }
9201                 if(k <= 1) {
9202                         result = GameIsDrawn;
9203                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9204                         resultDetails = buf;
9205                 }
9206             }
9207         }
9208
9209
9210         if(serverMoves != NULL && !loadFlag) { char c = '=';
9211             if(result==WhiteWins) c = '+';
9212             if(result==BlackWins) c = '-';
9213             if(resultDetails != NULL)
9214                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9215         }
9216         if (resultDetails != NULL) {
9217             gameInfo.result = result;
9218             gameInfo.resultDetails = StrSave(resultDetails);
9219
9220             /* display last move only if game was not loaded from file */
9221             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9222                 DisplayMove(currentMove - 1);
9223
9224             if (forwardMostMove != 0) {
9225                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9226                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9227                                                                 ) {
9228                     if (*appData.saveGameFile != NULLCHAR) {
9229                         SaveGameToFile(appData.saveGameFile, TRUE);
9230                     } else if (appData.autoSaveGames) {
9231                         AutoSaveGame();
9232                     }
9233                     if (*appData.savePositionFile != NULLCHAR) {
9234                         SavePositionToFile(appData.savePositionFile);
9235                     }
9236                 }
9237             }
9238
9239             /* Tell program how game ended in case it is learning */
9240             /* [HGM] Moved this to after saving the PGN, just in case */
9241             /* engine died and we got here through time loss. In that */
9242             /* case we will get a fatal error writing the pipe, which */
9243             /* would otherwise lose us the PGN.                       */
9244             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9245             /* output during GameEnds should never be fatal anymore   */
9246             if (gameMode == MachinePlaysWhite ||
9247                 gameMode == MachinePlaysBlack ||
9248                 gameMode == TwoMachinesPlay ||
9249                 gameMode == IcsPlayingWhite ||
9250                 gameMode == IcsPlayingBlack ||
9251                 gameMode == BeginningOfGame) {
9252                 char buf[MSG_SIZ];
9253                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9254                         resultDetails);
9255                 if (first.pr != NoProc) {
9256                     SendToProgram(buf, &first);
9257                 }
9258                 if (second.pr != NoProc &&
9259                     gameMode == TwoMachinesPlay) {
9260                     SendToProgram(buf, &second);
9261                 }
9262             }
9263         }
9264
9265         if (appData.icsActive) {
9266             if (appData.quietPlay &&
9267                 (gameMode == IcsPlayingWhite ||
9268                  gameMode == IcsPlayingBlack)) {
9269                 SendToICS(ics_prefix);
9270                 SendToICS("set shout 1\n");
9271             }
9272             nextGameMode = IcsIdle;
9273             ics_user_moved = FALSE;
9274             /* clean up premove.  It's ugly when the game has ended and the
9275              * premove highlights are still on the board.
9276              */
9277             if (gotPremove) {
9278               gotPremove = FALSE;
9279               ClearPremoveHighlights();
9280               DrawPosition(FALSE, boards[currentMove]);
9281             }
9282             if (whosays == GE_ICS) {
9283                 switch (result) {
9284                 case WhiteWins:
9285                     if (gameMode == IcsPlayingWhite)
9286                         PlayIcsWinSound();
9287                     else if(gameMode == IcsPlayingBlack)
9288                         PlayIcsLossSound();
9289                     break;
9290                 case BlackWins:
9291                     if (gameMode == IcsPlayingBlack)
9292                         PlayIcsWinSound();
9293                     else if(gameMode == IcsPlayingWhite)
9294                         PlayIcsLossSound();
9295                     break;
9296                 case GameIsDrawn:
9297                     PlayIcsDrawSound();
9298                     break;
9299                 default:
9300                     PlayIcsUnfinishedSound();
9301                 }
9302             }
9303         } else if (gameMode == EditGame ||
9304                    gameMode == PlayFromGameFile ||
9305                    gameMode == AnalyzeMode ||
9306                    gameMode == AnalyzeFile) {
9307             nextGameMode = gameMode;
9308         } else {
9309             nextGameMode = EndOfGame;
9310         }
9311         pausing = FALSE;
9312         ModeHighlight();
9313     } else {
9314         nextGameMode = gameMode;
9315     }
9316
9317     if (appData.noChessProgram) {
9318         gameMode = nextGameMode;
9319         ModeHighlight();
9320         endingGame = 0; /* [HGM] crash */
9321         return;
9322     }
9323
9324     if (first.reuse) {
9325         /* Put first chess program into idle state */
9326         if (first.pr != NoProc &&
9327             (gameMode == MachinePlaysWhite ||
9328              gameMode == MachinePlaysBlack ||
9329              gameMode == TwoMachinesPlay ||
9330              gameMode == IcsPlayingWhite ||
9331              gameMode == IcsPlayingBlack ||
9332              gameMode == BeginningOfGame)) {
9333             SendToProgram("force\n", &first);
9334             if (first.usePing) {
9335               char buf[MSG_SIZ];
9336               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9337               SendToProgram(buf, &first);
9338             }
9339         }
9340     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9341         /* Kill off first chess program */
9342         if (first.isr != NULL)
9343           RemoveInputSource(first.isr);
9344         first.isr = NULL;
9345
9346         if (first.pr != NoProc) {
9347             ExitAnalyzeMode();
9348             DoSleep( appData.delayBeforeQuit );
9349             SendToProgram("quit\n", &first);
9350             DoSleep( appData.delayAfterQuit );
9351             DestroyChildProcess(first.pr, first.useSigterm);
9352         }
9353         first.pr = NoProc;
9354     }
9355     if (second.reuse) {
9356         /* Put second chess program into idle state */
9357         if (second.pr != NoProc &&
9358             gameMode == TwoMachinesPlay) {
9359             SendToProgram("force\n", &second);
9360             if (second.usePing) {
9361               char buf[MSG_SIZ];
9362               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9363               SendToProgram(buf, &second);
9364             }
9365         }
9366     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9367         /* Kill off second chess program */
9368         if (second.isr != NULL)
9369           RemoveInputSource(second.isr);
9370         second.isr = NULL;
9371
9372         if (second.pr != NoProc) {
9373             DoSleep( appData.delayBeforeQuit );
9374             SendToProgram("quit\n", &second);
9375             DoSleep( appData.delayAfterQuit );
9376             DestroyChildProcess(second.pr, second.useSigterm);
9377         }
9378         second.pr = NoProc;
9379     }
9380
9381     if (matchMode && gameMode == TwoMachinesPlay) {
9382         switch (result) {
9383         case WhiteWins:
9384           if (first.twoMachinesColor[0] == 'w') {
9385             first.matchWins++;
9386           } else {
9387             second.matchWins++;
9388           }
9389           break;
9390         case BlackWins:
9391           if (first.twoMachinesColor[0] == 'b') {
9392             first.matchWins++;
9393           } else {
9394             second.matchWins++;
9395           }
9396           break;
9397         default:
9398           break;
9399         }
9400         if (matchGame < appData.matchGames) {
9401             char *tmp;
9402             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9403                 tmp = first.twoMachinesColor;
9404                 first.twoMachinesColor = second.twoMachinesColor;
9405                 second.twoMachinesColor = tmp;
9406             }
9407             gameMode = nextGameMode;
9408             matchGame++;
9409             if(appData.matchPause>10000 || appData.matchPause<10)
9410                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9411             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9412             endingGame = 0; /* [HGM] crash */
9413             return;
9414         } else {
9415             gameMode = nextGameMode;
9416             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9417                      first.tidy, second.tidy,
9418                      first.matchWins, second.matchWins,
9419                      appData.matchGames - (first.matchWins + second.matchWins));
9420             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9421         }
9422     }
9423     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9424         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9425       ExitAnalyzeMode();
9426     gameMode = nextGameMode;
9427     ModeHighlight();
9428     endingGame = 0;  /* [HGM] crash */
9429     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9430       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9431         matchMode = FALSE; appData.matchGames = matchGame = 0;
9432         DisplayNote(buf);
9433       }
9434     }
9435 }
9436
9437 /* Assumes program was just initialized (initString sent).
9438    Leaves program in force mode. */
9439 void
9440 FeedMovesToProgram(cps, upto)
9441      ChessProgramState *cps;
9442      int upto;
9443 {
9444     int i;
9445
9446     if (appData.debugMode)
9447       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9448               startedFromSetupPosition ? "position and " : "",
9449               backwardMostMove, upto, cps->which);
9450     if(currentlyInitializedVariant != gameInfo.variant) {
9451       char buf[MSG_SIZ];
9452         // [HGM] variantswitch: make engine aware of new variant
9453         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9454                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9455         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9456         SendToProgram(buf, cps);
9457         currentlyInitializedVariant = gameInfo.variant;
9458     }
9459     SendToProgram("force\n", cps);
9460     if (startedFromSetupPosition) {
9461         SendBoard(cps, backwardMostMove);
9462     if (appData.debugMode) {
9463         fprintf(debugFP, "feedMoves\n");
9464     }
9465     }
9466     for (i = backwardMostMove; i < upto; i++) {
9467         SendMoveToProgram(i, cps);
9468     }
9469 }
9470
9471
9472 void
9473 ResurrectChessProgram()
9474 {
9475      /* The chess program may have exited.
9476         If so, restart it and feed it all the moves made so far. */
9477
9478     if (appData.noChessProgram || first.pr != NoProc) return;
9479
9480     StartChessProgram(&first);
9481     InitChessProgram(&first, FALSE);
9482     FeedMovesToProgram(&first, currentMove);
9483
9484     if (!first.sendTime) {
9485         /* can't tell gnuchess what its clock should read,
9486            so we bow to its notion. */
9487         ResetClocks();
9488         timeRemaining[0][currentMove] = whiteTimeRemaining;
9489         timeRemaining[1][currentMove] = blackTimeRemaining;
9490     }
9491
9492     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9493                 appData.icsEngineAnalyze) && first.analysisSupport) {
9494       SendToProgram("analyze\n", &first);
9495       first.analyzing = TRUE;
9496     }
9497 }
9498
9499 /*
9500  * Button procedures
9501  */
9502 void
9503 Reset(redraw, init)
9504      int redraw, init;
9505 {
9506     int i;
9507
9508     if (appData.debugMode) {
9509         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9510                 redraw, init, gameMode);
9511     }
9512     CleanupTail(); // [HGM] vari: delete any stored variations
9513     pausing = pauseExamInvalid = FALSE;
9514     startedFromSetupPosition = blackPlaysFirst = FALSE;
9515     firstMove = TRUE;
9516     whiteFlag = blackFlag = FALSE;
9517     userOfferedDraw = FALSE;
9518     hintRequested = bookRequested = FALSE;
9519     first.maybeThinking = FALSE;
9520     second.maybeThinking = FALSE;
9521     first.bookSuspend = FALSE; // [HGM] book
9522     second.bookSuspend = FALSE;
9523     thinkOutput[0] = NULLCHAR;
9524     lastHint[0] = NULLCHAR;
9525     ClearGameInfo(&gameInfo);
9526     gameInfo.variant = StringToVariant(appData.variant);
9527     ics_user_moved = ics_clock_paused = FALSE;
9528     ics_getting_history = H_FALSE;
9529     ics_gamenum = -1;
9530     white_holding[0] = black_holding[0] = NULLCHAR;
9531     ClearProgramStats();
9532     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9533
9534     ResetFrontEnd();
9535     ClearHighlights();
9536     flipView = appData.flipView;
9537     ClearPremoveHighlights();
9538     gotPremove = FALSE;
9539     alarmSounded = FALSE;
9540
9541     GameEnds(EndOfFile, NULL, GE_PLAYER);
9542     if(appData.serverMovesName != NULL) {
9543         /* [HGM] prepare to make moves file for broadcasting */
9544         clock_t t = clock();
9545         if(serverMoves != NULL) fclose(serverMoves);
9546         serverMoves = fopen(appData.serverMovesName, "r");
9547         if(serverMoves != NULL) {
9548             fclose(serverMoves);
9549             /* delay 15 sec before overwriting, so all clients can see end */
9550             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9551         }
9552         serverMoves = fopen(appData.serverMovesName, "w");
9553     }
9554
9555     ExitAnalyzeMode();
9556     gameMode = BeginningOfGame;
9557     ModeHighlight();
9558     if(appData.icsActive) gameInfo.variant = VariantNormal;
9559     currentMove = forwardMostMove = backwardMostMove = 0;
9560     InitPosition(redraw);
9561     for (i = 0; i < MAX_MOVES; i++) {
9562         if (commentList[i] != NULL) {
9563             free(commentList[i]);
9564             commentList[i] = NULL;
9565         }
9566     }
9567     ResetClocks();
9568     timeRemaining[0][0] = whiteTimeRemaining;
9569     timeRemaining[1][0] = blackTimeRemaining;
9570     if (first.pr == NULL) {
9571         StartChessProgram(&first);
9572     }
9573     if (init) {
9574             InitChessProgram(&first, startedFromSetupPosition);
9575     }
9576     DisplayTitle("");
9577     DisplayMessage("", "");
9578     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9579     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9580 }
9581
9582 void
9583 AutoPlayGameLoop()
9584 {
9585     for (;;) {
9586         if (!AutoPlayOneMove())
9587           return;
9588         if (matchMode || appData.timeDelay == 0)
9589           continue;
9590         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9591           return;
9592         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9593         break;
9594     }
9595 }
9596
9597
9598 int
9599 AutoPlayOneMove()
9600 {
9601     int fromX, fromY, toX, toY;
9602
9603     if (appData.debugMode) {
9604       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9605     }
9606
9607     if (gameMode != PlayFromGameFile)
9608       return FALSE;
9609
9610     if (currentMove >= forwardMostMove) {
9611       gameMode = EditGame;
9612       ModeHighlight();
9613
9614       /* [AS] Clear current move marker at the end of a game */
9615       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9616
9617       return FALSE;
9618     }
9619
9620     toX = moveList[currentMove][2] - AAA;
9621     toY = moveList[currentMove][3] - ONE;
9622
9623     if (moveList[currentMove][1] == '@') {
9624         if (appData.highlightLastMove) {
9625             SetHighlights(-1, -1, toX, toY);
9626         }
9627     } else {
9628         fromX = moveList[currentMove][0] - AAA;
9629         fromY = moveList[currentMove][1] - ONE;
9630
9631         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9632
9633         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9634
9635         if (appData.highlightLastMove) {
9636             SetHighlights(fromX, fromY, toX, toY);
9637         }
9638     }
9639     DisplayMove(currentMove);
9640     SendMoveToProgram(currentMove++, &first);
9641     DisplayBothClocks();
9642     DrawPosition(FALSE, boards[currentMove]);
9643     // [HGM] PV info: always display, routine tests if empty
9644     DisplayComment(currentMove - 1, commentList[currentMove]);
9645     return TRUE;
9646 }
9647
9648
9649 int
9650 LoadGameOneMove(readAhead)
9651      ChessMove readAhead;
9652 {
9653     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9654     char promoChar = NULLCHAR;
9655     ChessMove moveType;
9656     char move[MSG_SIZ];
9657     char *p, *q;
9658
9659     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9660         gameMode != AnalyzeMode && gameMode != Training) {
9661         gameFileFP = NULL;
9662         return FALSE;
9663     }
9664
9665     yyboardindex = forwardMostMove;
9666     if (readAhead != EndOfFile) {
9667       moveType = readAhead;
9668     } else {
9669       if (gameFileFP == NULL)
9670           return FALSE;
9671       moveType = (ChessMove) yylex();
9672     }
9673
9674     done = FALSE;
9675     switch (moveType) {
9676       case Comment:
9677         if (appData.debugMode)
9678           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9679         p = yy_text;
9680
9681         /* append the comment but don't display it */
9682         AppendComment(currentMove, p, FALSE);
9683         return TRUE;
9684
9685       case WhiteCapturesEnPassant:
9686       case BlackCapturesEnPassant:
9687       case WhitePromotion:
9688       case BlackPromotion:
9689       case WhiteNonPromotion:
9690       case BlackNonPromotion:
9691       case NormalMove:
9692       case WhiteKingSideCastle:
9693       case WhiteQueenSideCastle:
9694       case BlackKingSideCastle:
9695       case BlackQueenSideCastle:
9696       case WhiteKingSideCastleWild:
9697       case WhiteQueenSideCastleWild:
9698       case BlackKingSideCastleWild:
9699       case BlackQueenSideCastleWild:
9700       /* PUSH Fabien */
9701       case WhiteHSideCastleFR:
9702       case WhiteASideCastleFR:
9703       case BlackHSideCastleFR:
9704       case BlackASideCastleFR:
9705       /* POP Fabien */
9706         if (appData.debugMode)
9707           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9708         fromX = currentMoveString[0] - AAA;
9709         fromY = currentMoveString[1] - ONE;
9710         toX = currentMoveString[2] - AAA;
9711         toY = currentMoveString[3] - ONE;
9712         promoChar = currentMoveString[4];
9713         break;
9714
9715       case WhiteDrop:
9716       case BlackDrop:
9717         if (appData.debugMode)
9718           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9719         fromX = moveType == WhiteDrop ?
9720           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9721         (int) CharToPiece(ToLower(currentMoveString[0]));
9722         fromY = DROP_RANK;
9723         toX = currentMoveString[2] - AAA;
9724         toY = currentMoveString[3] - ONE;
9725         break;
9726
9727       case WhiteWins:
9728       case BlackWins:
9729       case GameIsDrawn:
9730       case GameUnfinished:
9731         if (appData.debugMode)
9732           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9733         p = strchr(yy_text, '{');
9734         if (p == NULL) p = strchr(yy_text, '(');
9735         if (p == NULL) {
9736             p = yy_text;
9737             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9738         } else {
9739             q = strchr(p, *p == '{' ? '}' : ')');
9740             if (q != NULL) *q = NULLCHAR;
9741             p++;
9742         }
9743         GameEnds(moveType, p, GE_FILE);
9744         done = TRUE;
9745         if (cmailMsgLoaded) {
9746             ClearHighlights();
9747             flipView = WhiteOnMove(currentMove);
9748             if (moveType == GameUnfinished) flipView = !flipView;
9749             if (appData.debugMode)
9750               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9751         }
9752         break;
9753
9754       case EndOfFile:
9755         if (appData.debugMode)
9756           fprintf(debugFP, "Parser hit end of file\n");
9757         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9758           case MT_NONE:
9759           case MT_CHECK:
9760             break;
9761           case MT_CHECKMATE:
9762           case MT_STAINMATE:
9763             if (WhiteOnMove(currentMove)) {
9764                 GameEnds(BlackWins, "Black mates", GE_FILE);
9765             } else {
9766                 GameEnds(WhiteWins, "White mates", GE_FILE);
9767             }
9768             break;
9769           case MT_STALEMATE:
9770             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9771             break;
9772         }
9773         done = TRUE;
9774         break;
9775
9776       case MoveNumberOne:
9777         if (lastLoadGameStart == GNUChessGame) {
9778             /* GNUChessGames have numbers, but they aren't move numbers */
9779             if (appData.debugMode)
9780               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9781                       yy_text, (int) moveType);
9782             return LoadGameOneMove(EndOfFile); /* tail recursion */
9783         }
9784         /* else fall thru */
9785
9786       case XBoardGame:
9787       case GNUChessGame:
9788       case PGNTag:
9789         /* Reached start of next game in file */
9790         if (appData.debugMode)
9791           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9792         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9793           case MT_NONE:
9794           case MT_CHECK:
9795             break;
9796           case MT_CHECKMATE:
9797           case MT_STAINMATE:
9798             if (WhiteOnMove(currentMove)) {
9799                 GameEnds(BlackWins, "Black mates", GE_FILE);
9800             } else {
9801                 GameEnds(WhiteWins, "White mates", GE_FILE);
9802             }
9803             break;
9804           case MT_STALEMATE:
9805             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9806             break;
9807         }
9808         done = TRUE;
9809         break;
9810
9811       case PositionDiagram:     /* should not happen; ignore */
9812       case ElapsedTime:         /* ignore */
9813       case NAG:                 /* ignore */
9814         if (appData.debugMode)
9815           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9816                   yy_text, (int) moveType);
9817         return LoadGameOneMove(EndOfFile); /* tail recursion */
9818
9819       case IllegalMove:
9820         if (appData.testLegality) {
9821             if (appData.debugMode)
9822               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9823             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9824                     (forwardMostMove / 2) + 1,
9825                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9826             DisplayError(move, 0);
9827             done = TRUE;
9828         } else {
9829             if (appData.debugMode)
9830               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9831                       yy_text, currentMoveString);
9832             fromX = currentMoveString[0] - AAA;
9833             fromY = currentMoveString[1] - ONE;
9834             toX = currentMoveString[2] - AAA;
9835             toY = currentMoveString[3] - ONE;
9836             promoChar = currentMoveString[4];
9837         }
9838         break;
9839
9840       case AmbiguousMove:
9841         if (appData.debugMode)
9842           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9843         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9844                 (forwardMostMove / 2) + 1,
9845                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9846         DisplayError(move, 0);
9847         done = TRUE;
9848         break;
9849
9850       default:
9851       case ImpossibleMove:
9852         if (appData.debugMode)
9853           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9854         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9855                 (forwardMostMove / 2) + 1,
9856                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9857         DisplayError(move, 0);
9858         done = TRUE;
9859         break;
9860     }
9861
9862     if (done) {
9863         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9864             DrawPosition(FALSE, boards[currentMove]);
9865             DisplayBothClocks();
9866             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9867               DisplayComment(currentMove - 1, commentList[currentMove]);
9868         }
9869         (void) StopLoadGameTimer();
9870         gameFileFP = NULL;
9871         cmailOldMove = forwardMostMove;
9872         return FALSE;
9873     } else {
9874         /* currentMoveString is set as a side-effect of yylex */
9875         strcat(currentMoveString, "\n");
9876         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9877
9878         thinkOutput[0] = NULLCHAR;
9879         MakeMove(fromX, fromY, toX, toY, promoChar);
9880         currentMove = forwardMostMove;
9881         return TRUE;
9882     }
9883 }
9884
9885 /* Load the nth game from the given file */
9886 int
9887 LoadGameFromFile(filename, n, title, useList)
9888      char *filename;
9889      int n;
9890      char *title;
9891      /*Boolean*/ int useList;
9892 {
9893     FILE *f;
9894     char buf[MSG_SIZ];
9895
9896     if (strcmp(filename, "-") == 0) {
9897         f = stdin;
9898         title = "stdin";
9899     } else {
9900         f = fopen(filename, "rb");
9901         if (f == NULL) {
9902           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9903             DisplayError(buf, errno);
9904             return FALSE;
9905         }
9906     }
9907     if (fseek(f, 0, 0) == -1) {
9908         /* f is not seekable; probably a pipe */
9909         useList = FALSE;
9910     }
9911     if (useList && n == 0) {
9912         int error = GameListBuild(f);
9913         if (error) {
9914             DisplayError(_("Cannot build game list"), error);
9915         } else if (!ListEmpty(&gameList) &&
9916                    ((ListGame *) gameList.tailPred)->number > 1) {
9917             GameListPopUp(f, title);
9918             return TRUE;
9919         }
9920         GameListDestroy();
9921         n = 1;
9922     }
9923     if (n == 0) n = 1;
9924     return LoadGame(f, n, title, FALSE);
9925 }
9926
9927
9928 void
9929 MakeRegisteredMove()
9930 {
9931     int fromX, fromY, toX, toY;
9932     char promoChar;
9933     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9934         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9935           case CMAIL_MOVE:
9936           case CMAIL_DRAW:
9937             if (appData.debugMode)
9938               fprintf(debugFP, "Restoring %s for game %d\n",
9939                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9940
9941             thinkOutput[0] = NULLCHAR;
9942             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9943             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9944             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9945             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9946             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9947             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9948             MakeMove(fromX, fromY, toX, toY, promoChar);
9949             ShowMove(fromX, fromY, toX, toY);
9950
9951             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9952               case MT_NONE:
9953               case MT_CHECK:
9954                 break;
9955
9956               case MT_CHECKMATE:
9957               case MT_STAINMATE:
9958                 if (WhiteOnMove(currentMove)) {
9959                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9960                 } else {
9961                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9962                 }
9963                 break;
9964
9965               case MT_STALEMATE:
9966                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9967                 break;
9968             }
9969
9970             break;
9971
9972           case CMAIL_RESIGN:
9973             if (WhiteOnMove(currentMove)) {
9974                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9975             } else {
9976                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9977             }
9978             break;
9979
9980           case CMAIL_ACCEPT:
9981             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9982             break;
9983
9984           default:
9985             break;
9986         }
9987     }
9988
9989     return;
9990 }
9991
9992 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9993 int
9994 CmailLoadGame(f, gameNumber, title, useList)
9995      FILE *f;
9996      int gameNumber;
9997      char *title;
9998      int useList;
9999 {
10000     int retVal;
10001
10002     if (gameNumber > nCmailGames) {
10003         DisplayError(_("No more games in this message"), 0);
10004         return FALSE;
10005     }
10006     if (f == lastLoadGameFP) {
10007         int offset = gameNumber - lastLoadGameNumber;
10008         if (offset == 0) {
10009             cmailMsg[0] = NULLCHAR;
10010             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10011                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10012                 nCmailMovesRegistered--;
10013             }
10014             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10015             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10016                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10017             }
10018         } else {
10019             if (! RegisterMove()) return FALSE;
10020         }
10021     }
10022
10023     retVal = LoadGame(f, gameNumber, title, useList);
10024
10025     /* Make move registered during previous look at this game, if any */
10026     MakeRegisteredMove();
10027
10028     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10029         commentList[currentMove]
10030           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10031         DisplayComment(currentMove - 1, commentList[currentMove]);
10032     }
10033
10034     return retVal;
10035 }
10036
10037 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10038 int
10039 ReloadGame(offset)
10040      int offset;
10041 {
10042     int gameNumber = lastLoadGameNumber + offset;
10043     if (lastLoadGameFP == NULL) {
10044         DisplayError(_("No game has been loaded yet"), 0);
10045         return FALSE;
10046     }
10047     if (gameNumber <= 0) {
10048         DisplayError(_("Can't back up any further"), 0);
10049         return FALSE;
10050     }
10051     if (cmailMsgLoaded) {
10052         return CmailLoadGame(lastLoadGameFP, gameNumber,
10053                              lastLoadGameTitle, lastLoadGameUseList);
10054     } else {
10055         return LoadGame(lastLoadGameFP, gameNumber,
10056                         lastLoadGameTitle, lastLoadGameUseList);
10057     }
10058 }
10059
10060
10061
10062 /* Load the nth game from open file f */
10063 int
10064 LoadGame(f, gameNumber, title, useList)
10065      FILE *f;
10066      int gameNumber;
10067      char *title;
10068      int useList;
10069 {
10070     ChessMove cm;
10071     char buf[MSG_SIZ];
10072     int gn = gameNumber;
10073     ListGame *lg = NULL;
10074     int numPGNTags = 0;
10075     int err;
10076     GameMode oldGameMode;
10077     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10078
10079     if (appData.debugMode)
10080         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10081
10082     if (gameMode == Training )
10083         SetTrainingModeOff();
10084
10085     oldGameMode = gameMode;
10086     if (gameMode != BeginningOfGame) {
10087       Reset(FALSE, TRUE);
10088     }
10089
10090     gameFileFP = f;
10091     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10092         fclose(lastLoadGameFP);
10093     }
10094
10095     if (useList) {
10096         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10097
10098         if (lg) {
10099             fseek(f, lg->offset, 0);
10100             GameListHighlight(gameNumber);
10101             gn = 1;
10102         }
10103         else {
10104             DisplayError(_("Game number out of range"), 0);
10105             return FALSE;
10106         }
10107     } else {
10108         GameListDestroy();
10109         if (fseek(f, 0, 0) == -1) {
10110             if (f == lastLoadGameFP ?
10111                 gameNumber == lastLoadGameNumber + 1 :
10112                 gameNumber == 1) {
10113                 gn = 1;
10114             } else {
10115                 DisplayError(_("Can't seek on game file"), 0);
10116                 return FALSE;
10117             }
10118         }
10119     }
10120     lastLoadGameFP = f;
10121     lastLoadGameNumber = gameNumber;
10122     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10123     lastLoadGameUseList = useList;
10124
10125     yynewfile(f);
10126
10127     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10128       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10129                 lg->gameInfo.black);
10130             DisplayTitle(buf);
10131     } else if (*title != NULLCHAR) {
10132         if (gameNumber > 1) {
10133           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10134             DisplayTitle(buf);
10135         } else {
10136             DisplayTitle(title);
10137         }
10138     }
10139
10140     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10141         gameMode = PlayFromGameFile;
10142         ModeHighlight();
10143     }
10144
10145     currentMove = forwardMostMove = backwardMostMove = 0;
10146     CopyBoard(boards[0], initialPosition);
10147     StopClocks();
10148
10149     /*
10150      * Skip the first gn-1 games in the file.
10151      * Also skip over anything that precedes an identifiable
10152      * start of game marker, to avoid being confused by
10153      * garbage at the start of the file.  Currently
10154      * recognized start of game markers are the move number "1",
10155      * the pattern "gnuchess .* game", the pattern
10156      * "^[#;%] [^ ]* game file", and a PGN tag block.
10157      * A game that starts with one of the latter two patterns
10158      * will also have a move number 1, possibly
10159      * following a position diagram.
10160      * 5-4-02: Let's try being more lenient and allowing a game to
10161      * start with an unnumbered move.  Does that break anything?
10162      */
10163     cm = lastLoadGameStart = EndOfFile;
10164     while (gn > 0) {
10165         yyboardindex = forwardMostMove;
10166         cm = (ChessMove) yylex();
10167         switch (cm) {
10168           case EndOfFile:
10169             if (cmailMsgLoaded) {
10170                 nCmailGames = CMAIL_MAX_GAMES - gn;
10171             } else {
10172                 Reset(TRUE, TRUE);
10173                 DisplayError(_("Game not found in file"), 0);
10174             }
10175             return FALSE;
10176
10177           case GNUChessGame:
10178           case XBoardGame:
10179             gn--;
10180             lastLoadGameStart = cm;
10181             break;
10182
10183           case MoveNumberOne:
10184             switch (lastLoadGameStart) {
10185               case GNUChessGame:
10186               case XBoardGame:
10187               case PGNTag:
10188                 break;
10189               case MoveNumberOne:
10190               case EndOfFile:
10191                 gn--;           /* count this game */
10192                 lastLoadGameStart = cm;
10193                 break;
10194               default:
10195                 /* impossible */
10196                 break;
10197             }
10198             break;
10199
10200           case PGNTag:
10201             switch (lastLoadGameStart) {
10202               case GNUChessGame:
10203               case PGNTag:
10204               case MoveNumberOne:
10205               case EndOfFile:
10206                 gn--;           /* count this game */
10207                 lastLoadGameStart = cm;
10208                 break;
10209               case XBoardGame:
10210                 lastLoadGameStart = cm; /* game counted already */
10211                 break;
10212               default:
10213                 /* impossible */
10214                 break;
10215             }
10216             if (gn > 0) {
10217                 do {
10218                     yyboardindex = forwardMostMove;
10219                     cm = (ChessMove) yylex();
10220                 } while (cm == PGNTag || cm == Comment);
10221             }
10222             break;
10223
10224           case WhiteWins:
10225           case BlackWins:
10226           case GameIsDrawn:
10227             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10228                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10229                     != CMAIL_OLD_RESULT) {
10230                     nCmailResults ++ ;
10231                     cmailResult[  CMAIL_MAX_GAMES
10232                                 - gn - 1] = CMAIL_OLD_RESULT;
10233                 }
10234             }
10235             break;
10236
10237           case NormalMove:
10238             /* Only a NormalMove can be at the start of a game
10239              * without a position diagram. */
10240             if (lastLoadGameStart == EndOfFile ) {
10241               gn--;
10242               lastLoadGameStart = MoveNumberOne;
10243             }
10244             break;
10245
10246           default:
10247             break;
10248         }
10249     }
10250
10251     if (appData.debugMode)
10252       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10253
10254     if (cm == XBoardGame) {
10255         /* Skip any header junk before position diagram and/or move 1 */
10256         for (;;) {
10257             yyboardindex = forwardMostMove;
10258             cm = (ChessMove) yylex();
10259
10260             if (cm == EndOfFile ||
10261                 cm == GNUChessGame || cm == XBoardGame) {
10262                 /* Empty game; pretend end-of-file and handle later */
10263                 cm = EndOfFile;
10264                 break;
10265             }
10266
10267             if (cm == MoveNumberOne || cm == PositionDiagram ||
10268                 cm == PGNTag || cm == Comment)
10269               break;
10270         }
10271     } else if (cm == GNUChessGame) {
10272         if (gameInfo.event != NULL) {
10273             free(gameInfo.event);
10274         }
10275         gameInfo.event = StrSave(yy_text);
10276     }
10277
10278     startedFromSetupPosition = FALSE;
10279     while (cm == PGNTag) {
10280         if (appData.debugMode)
10281           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10282         err = ParsePGNTag(yy_text, &gameInfo);
10283         if (!err) numPGNTags++;
10284
10285         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10286         if(gameInfo.variant != oldVariant) {
10287             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10288             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10289             InitPosition(TRUE);
10290             oldVariant = gameInfo.variant;
10291             if (appData.debugMode)
10292               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10293         }
10294
10295
10296         if (gameInfo.fen != NULL) {
10297           Board initial_position;
10298           startedFromSetupPosition = TRUE;
10299           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10300             Reset(TRUE, TRUE);
10301             DisplayError(_("Bad FEN position in file"), 0);
10302             return FALSE;
10303           }
10304           CopyBoard(boards[0], initial_position);
10305           if (blackPlaysFirst) {
10306             currentMove = forwardMostMove = backwardMostMove = 1;
10307             CopyBoard(boards[1], initial_position);
10308             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10309             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10310             timeRemaining[0][1] = whiteTimeRemaining;
10311             timeRemaining[1][1] = blackTimeRemaining;
10312             if (commentList[0] != NULL) {
10313               commentList[1] = commentList[0];
10314               commentList[0] = NULL;
10315             }
10316           } else {
10317             currentMove = forwardMostMove = backwardMostMove = 0;
10318           }
10319           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10320           {   int i;
10321               initialRulePlies = FENrulePlies;
10322               for( i=0; i< nrCastlingRights; i++ )
10323                   initialRights[i] = initial_position[CASTLING][i];
10324           }
10325           yyboardindex = forwardMostMove;
10326           free(gameInfo.fen);
10327           gameInfo.fen = NULL;
10328         }
10329
10330         yyboardindex = forwardMostMove;
10331         cm = (ChessMove) yylex();
10332
10333         /* Handle comments interspersed among the tags */
10334         while (cm == Comment) {
10335             char *p;
10336             if (appData.debugMode)
10337               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10338             p = yy_text;
10339             AppendComment(currentMove, p, FALSE);
10340             yyboardindex = forwardMostMove;
10341             cm = (ChessMove) yylex();
10342         }
10343     }
10344
10345     /* don't rely on existence of Event tag since if game was
10346      * pasted from clipboard the Event tag may not exist
10347      */
10348     if (numPGNTags > 0){
10349         char *tags;
10350         if (gameInfo.variant == VariantNormal) {
10351           VariantClass v = StringToVariant(gameInfo.event);
10352           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10353           if(v < VariantShogi) gameInfo.variant = v;
10354         }
10355         if (!matchMode) {
10356           if( appData.autoDisplayTags ) {
10357             tags = PGNTags(&gameInfo);
10358             TagsPopUp(tags, CmailMsg());
10359             free(tags);
10360           }
10361         }
10362     } else {
10363         /* Make something up, but don't display it now */
10364         SetGameInfo();
10365         TagsPopDown();
10366     }
10367
10368     if (cm == PositionDiagram) {
10369         int i, j;
10370         char *p;
10371         Board initial_position;
10372
10373         if (appData.debugMode)
10374           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10375
10376         if (!startedFromSetupPosition) {
10377             p = yy_text;
10378             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10379               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10380                 switch (*p) {
10381                   case '[':
10382                   case '-':
10383                   case ' ':
10384                   case '\t':
10385                   case '\n':
10386                   case '\r':
10387                     break;
10388                   default:
10389                     initial_position[i][j++] = CharToPiece(*p);
10390                     break;
10391                 }
10392             while (*p == ' ' || *p == '\t' ||
10393                    *p == '\n' || *p == '\r') p++;
10394
10395             if (strncmp(p, "black", strlen("black"))==0)
10396               blackPlaysFirst = TRUE;
10397             else
10398               blackPlaysFirst = FALSE;
10399             startedFromSetupPosition = TRUE;
10400
10401             CopyBoard(boards[0], initial_position);
10402             if (blackPlaysFirst) {
10403                 currentMove = forwardMostMove = backwardMostMove = 1;
10404                 CopyBoard(boards[1], initial_position);
10405                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10406                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10407                 timeRemaining[0][1] = whiteTimeRemaining;
10408                 timeRemaining[1][1] = blackTimeRemaining;
10409                 if (commentList[0] != NULL) {
10410                     commentList[1] = commentList[0];
10411                     commentList[0] = NULL;
10412                 }
10413             } else {
10414                 currentMove = forwardMostMove = backwardMostMove = 0;
10415             }
10416         }
10417         yyboardindex = forwardMostMove;
10418         cm = (ChessMove) yylex();
10419     }
10420
10421     if (first.pr == NoProc) {
10422         StartChessProgram(&first);
10423     }
10424     InitChessProgram(&first, FALSE);
10425     SendToProgram("force\n", &first);
10426     if (startedFromSetupPosition) {
10427         SendBoard(&first, forwardMostMove);
10428     if (appData.debugMode) {
10429         fprintf(debugFP, "Load Game\n");
10430     }
10431         DisplayBothClocks();
10432     }
10433
10434     /* [HGM] server: flag to write setup moves in broadcast file as one */
10435     loadFlag = appData.suppressLoadMoves;
10436
10437     while (cm == Comment) {
10438         char *p;
10439         if (appData.debugMode)
10440           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10441         p = yy_text;
10442         AppendComment(currentMove, p, FALSE);
10443         yyboardindex = forwardMostMove;
10444         cm = (ChessMove) yylex();
10445     }
10446
10447     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10448         cm == WhiteWins || cm == BlackWins ||
10449         cm == GameIsDrawn || cm == GameUnfinished) {
10450         DisplayMessage("", _("No moves in game"));
10451         if (cmailMsgLoaded) {
10452             if (appData.debugMode)
10453               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10454             ClearHighlights();
10455             flipView = FALSE;
10456         }
10457         DrawPosition(FALSE, boards[currentMove]);
10458         DisplayBothClocks();
10459         gameMode = EditGame;
10460         ModeHighlight();
10461         gameFileFP = NULL;
10462         cmailOldMove = 0;
10463         return TRUE;
10464     }
10465
10466     // [HGM] PV info: routine tests if comment empty
10467     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10468         DisplayComment(currentMove - 1, commentList[currentMove]);
10469     }
10470     if (!matchMode && appData.timeDelay != 0)
10471       DrawPosition(FALSE, boards[currentMove]);
10472
10473     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10474       programStats.ok_to_send = 1;
10475     }
10476
10477     /* if the first token after the PGN tags is a move
10478      * and not move number 1, retrieve it from the parser
10479      */
10480     if (cm != MoveNumberOne)
10481         LoadGameOneMove(cm);
10482
10483     /* load the remaining moves from the file */
10484     while (LoadGameOneMove(EndOfFile)) {
10485       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10486       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10487     }
10488
10489     /* rewind to the start of the game */
10490     currentMove = backwardMostMove;
10491
10492     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10493
10494     if (oldGameMode == AnalyzeFile ||
10495         oldGameMode == AnalyzeMode) {
10496       AnalyzeFileEvent();
10497     }
10498
10499     if (matchMode || appData.timeDelay == 0) {
10500       ToEndEvent();
10501       gameMode = EditGame;
10502       ModeHighlight();
10503     } else if (appData.timeDelay > 0) {
10504       AutoPlayGameLoop();
10505     }
10506
10507     if (appData.debugMode)
10508         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10509
10510     loadFlag = 0; /* [HGM] true game starts */
10511     return TRUE;
10512 }
10513
10514 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10515 int
10516 ReloadPosition(offset)
10517      int offset;
10518 {
10519     int positionNumber = lastLoadPositionNumber + offset;
10520     if (lastLoadPositionFP == NULL) {
10521         DisplayError(_("No position has been loaded yet"), 0);
10522         return FALSE;
10523     }
10524     if (positionNumber <= 0) {
10525         DisplayError(_("Can't back up any further"), 0);
10526         return FALSE;
10527     }
10528     return LoadPosition(lastLoadPositionFP, positionNumber,
10529                         lastLoadPositionTitle);
10530 }
10531
10532 /* Load the nth position from the given file */
10533 int
10534 LoadPositionFromFile(filename, n, title)
10535      char *filename;
10536      int n;
10537      char *title;
10538 {
10539     FILE *f;
10540     char buf[MSG_SIZ];
10541
10542     if (strcmp(filename, "-") == 0) {
10543         return LoadPosition(stdin, n, "stdin");
10544     } else {
10545         f = fopen(filename, "rb");
10546         if (f == NULL) {
10547             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10548             DisplayError(buf, errno);
10549             return FALSE;
10550         } else {
10551             return LoadPosition(f, n, title);
10552         }
10553     }
10554 }
10555
10556 /* Load the nth position from the given open file, and close it */
10557 int
10558 LoadPosition(f, positionNumber, title)
10559      FILE *f;
10560      int positionNumber;
10561      char *title;
10562 {
10563     char *p, line[MSG_SIZ];
10564     Board initial_position;
10565     int i, j, fenMode, pn;
10566
10567     if (gameMode == Training )
10568         SetTrainingModeOff();
10569
10570     if (gameMode != BeginningOfGame) {
10571         Reset(FALSE, TRUE);
10572     }
10573     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10574         fclose(lastLoadPositionFP);
10575     }
10576     if (positionNumber == 0) positionNumber = 1;
10577     lastLoadPositionFP = f;
10578     lastLoadPositionNumber = positionNumber;
10579     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10580     if (first.pr == NoProc) {
10581       StartChessProgram(&first);
10582       InitChessProgram(&first, FALSE);
10583     }
10584     pn = positionNumber;
10585     if (positionNumber < 0) {
10586         /* Negative position number means to seek to that byte offset */
10587         if (fseek(f, -positionNumber, 0) == -1) {
10588             DisplayError(_("Can't seek on position file"), 0);
10589             return FALSE;
10590         };
10591         pn = 1;
10592     } else {
10593         if (fseek(f, 0, 0) == -1) {
10594             if (f == lastLoadPositionFP ?
10595                 positionNumber == lastLoadPositionNumber + 1 :
10596                 positionNumber == 1) {
10597                 pn = 1;
10598             } else {
10599                 DisplayError(_("Can't seek on position file"), 0);
10600                 return FALSE;
10601             }
10602         }
10603     }
10604     /* See if this file is FEN or old-style xboard */
10605     if (fgets(line, MSG_SIZ, f) == NULL) {
10606         DisplayError(_("Position not found in file"), 0);
10607         return FALSE;
10608     }
10609     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10610     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10611
10612     if (pn >= 2) {
10613         if (fenMode || line[0] == '#') pn--;
10614         while (pn > 0) {
10615             /* skip positions before number pn */
10616             if (fgets(line, MSG_SIZ, f) == NULL) {
10617                 Reset(TRUE, TRUE);
10618                 DisplayError(_("Position not found in file"), 0);
10619                 return FALSE;
10620             }
10621             if (fenMode || line[0] == '#') pn--;
10622         }
10623     }
10624
10625     if (fenMode) {
10626         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10627             DisplayError(_("Bad FEN position in file"), 0);
10628             return FALSE;
10629         }
10630     } else {
10631         (void) fgets(line, MSG_SIZ, f);
10632         (void) fgets(line, MSG_SIZ, f);
10633
10634         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10635             (void) fgets(line, MSG_SIZ, f);
10636             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10637                 if (*p == ' ')
10638                   continue;
10639                 initial_position[i][j++] = CharToPiece(*p);
10640             }
10641         }
10642
10643         blackPlaysFirst = FALSE;
10644         if (!feof(f)) {
10645             (void) fgets(line, MSG_SIZ, f);
10646             if (strncmp(line, "black", strlen("black"))==0)
10647               blackPlaysFirst = TRUE;
10648         }
10649     }
10650     startedFromSetupPosition = TRUE;
10651
10652     SendToProgram("force\n", &first);
10653     CopyBoard(boards[0], initial_position);
10654     if (blackPlaysFirst) {
10655         currentMove = forwardMostMove = backwardMostMove = 1;
10656         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10657         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10658         CopyBoard(boards[1], initial_position);
10659         DisplayMessage("", _("Black to play"));
10660     } else {
10661         currentMove = forwardMostMove = backwardMostMove = 0;
10662         DisplayMessage("", _("White to play"));
10663     }
10664     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10665     SendBoard(&first, forwardMostMove);
10666     if (appData.debugMode) {
10667 int i, j;
10668   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10669   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10670         fprintf(debugFP, "Load Position\n");
10671     }
10672
10673     if (positionNumber > 1) {
10674       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10675         DisplayTitle(line);
10676     } else {
10677         DisplayTitle(title);
10678     }
10679     gameMode = EditGame;
10680     ModeHighlight();
10681     ResetClocks();
10682     timeRemaining[0][1] = whiteTimeRemaining;
10683     timeRemaining[1][1] = blackTimeRemaining;
10684     DrawPosition(FALSE, boards[currentMove]);
10685
10686     return TRUE;
10687 }
10688
10689
10690 void
10691 CopyPlayerNameIntoFileName(dest, src)
10692      char **dest, *src;
10693 {
10694     while (*src != NULLCHAR && *src != ',') {
10695         if (*src == ' ') {
10696             *(*dest)++ = '_';
10697             src++;
10698         } else {
10699             *(*dest)++ = *src++;
10700         }
10701     }
10702 }
10703
10704 char *DefaultFileName(ext)
10705      char *ext;
10706 {
10707     static char def[MSG_SIZ];
10708     char *p;
10709
10710     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10711         p = def;
10712         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10713         *p++ = '-';
10714         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10715         *p++ = '.';
10716         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10717     } else {
10718         def[0] = NULLCHAR;
10719     }
10720     return def;
10721 }
10722
10723 /* Save the current game to the given file */
10724 int
10725 SaveGameToFile(filename, append)
10726      char *filename;
10727      int append;
10728 {
10729     FILE *f;
10730     char buf[MSG_SIZ];
10731
10732     if (strcmp(filename, "-") == 0) {
10733         return SaveGame(stdout, 0, NULL);
10734     } else {
10735         f = fopen(filename, append ? "a" : "w");
10736         if (f == NULL) {
10737             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10738             DisplayError(buf, errno);
10739             return FALSE;
10740         } else {
10741             return SaveGame(f, 0, NULL);
10742         }
10743     }
10744 }
10745
10746 char *
10747 SavePart(str)
10748      char *str;
10749 {
10750     static char buf[MSG_SIZ];
10751     char *p;
10752
10753     p = strchr(str, ' ');
10754     if (p == NULL) return str;
10755     strncpy(buf, str, p - str);
10756     buf[p - str] = NULLCHAR;
10757     return buf;
10758 }
10759
10760 #define PGN_MAX_LINE 75
10761
10762 #define PGN_SIDE_WHITE  0
10763 #define PGN_SIDE_BLACK  1
10764
10765 /* [AS] */
10766 static int FindFirstMoveOutOfBook( int side )
10767 {
10768     int result = -1;
10769
10770     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10771         int index = backwardMostMove;
10772         int has_book_hit = 0;
10773
10774         if( (index % 2) != side ) {
10775             index++;
10776         }
10777
10778         while( index < forwardMostMove ) {
10779             /* Check to see if engine is in book */
10780             int depth = pvInfoList[index].depth;
10781             int score = pvInfoList[index].score;
10782             int in_book = 0;
10783
10784             if( depth <= 2 ) {
10785                 in_book = 1;
10786             }
10787             else if( score == 0 && depth == 63 ) {
10788                 in_book = 1; /* Zappa */
10789             }
10790             else if( score == 2 && depth == 99 ) {
10791                 in_book = 1; /* Abrok */
10792             }
10793
10794             has_book_hit += in_book;
10795
10796             if( ! in_book ) {
10797                 result = index;
10798
10799                 break;
10800             }
10801
10802             index += 2;
10803         }
10804     }
10805
10806     return result;
10807 }
10808
10809 /* [AS] */
10810 void GetOutOfBookInfo( char * buf )
10811 {
10812     int oob[2];
10813     int i;
10814     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10815
10816     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10817     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10818
10819     *buf = '\0';
10820
10821     if( oob[0] >= 0 || oob[1] >= 0 ) {
10822         for( i=0; i<2; i++ ) {
10823             int idx = oob[i];
10824
10825             if( idx >= 0 ) {
10826                 if( i > 0 && oob[0] >= 0 ) {
10827                     strcat( buf, "   " );
10828                 }
10829
10830                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10831                 sprintf( buf+strlen(buf), "%s%.2f",
10832                     pvInfoList[idx].score >= 0 ? "+" : "",
10833                     pvInfoList[idx].score / 100.0 );
10834             }
10835         }
10836     }
10837 }
10838
10839 /* Save game in PGN style and close the file */
10840 int
10841 SaveGamePGN(f)
10842      FILE *f;
10843 {
10844     int i, offset, linelen, newblock;
10845     time_t tm;
10846 //    char *movetext;
10847     char numtext[32];
10848     int movelen, numlen, blank;
10849     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10850
10851     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10852
10853     tm = time((time_t *) NULL);
10854
10855     PrintPGNTags(f, &gameInfo);
10856
10857     if (backwardMostMove > 0 || startedFromSetupPosition) {
10858         char *fen = PositionToFEN(backwardMostMove, NULL);
10859         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10860         fprintf(f, "\n{--------------\n");
10861         PrintPosition(f, backwardMostMove);
10862         fprintf(f, "--------------}\n");
10863         free(fen);
10864     }
10865     else {
10866         /* [AS] Out of book annotation */
10867         if( appData.saveOutOfBookInfo ) {
10868             char buf[64];
10869
10870             GetOutOfBookInfo( buf );
10871
10872             if( buf[0] != '\0' ) {
10873                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10874             }
10875         }
10876
10877         fprintf(f, "\n");
10878     }
10879
10880     i = backwardMostMove;
10881     linelen = 0;
10882     newblock = TRUE;
10883
10884     while (i < forwardMostMove) {
10885         /* Print comments preceding this move */
10886         if (commentList[i] != NULL) {
10887             if (linelen > 0) fprintf(f, "\n");
10888             fprintf(f, "%s", commentList[i]);
10889             linelen = 0;
10890             newblock = TRUE;
10891         }
10892
10893         /* Format move number */
10894         if ((i % 2) == 0)
10895           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10896         else
10897           if (newblock)
10898             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10899           else
10900             numtext[0] = NULLCHAR;
10901
10902         numlen = strlen(numtext);
10903         newblock = FALSE;
10904
10905         /* Print move number */
10906         blank = linelen > 0 && numlen > 0;
10907         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10908             fprintf(f, "\n");
10909             linelen = 0;
10910             blank = 0;
10911         }
10912         if (blank) {
10913             fprintf(f, " ");
10914             linelen++;
10915         }
10916         fprintf(f, "%s", numtext);
10917         linelen += numlen;
10918
10919         /* Get move */
10920         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10921         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10922
10923         /* Print move */
10924         blank = linelen > 0 && movelen > 0;
10925         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10926             fprintf(f, "\n");
10927             linelen = 0;
10928             blank = 0;
10929         }
10930         if (blank) {
10931             fprintf(f, " ");
10932             linelen++;
10933         }
10934         fprintf(f, "%s", move_buffer);
10935         linelen += movelen;
10936
10937         /* [AS] Add PV info if present */
10938         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10939             /* [HGM] add time */
10940             char buf[MSG_SIZ]; int seconds;
10941
10942             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10943
10944             if( seconds <= 0)
10945               buf[0] = 0;
10946             else
10947               if( seconds < 30 )
10948                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10949               else
10950                 {
10951                   seconds = (seconds + 4)/10; // round to full seconds
10952                   if( seconds < 60 )
10953                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10954                   else
10955                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10956                 }
10957
10958             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10959                       pvInfoList[i].score >= 0 ? "+" : "",
10960                       pvInfoList[i].score / 100.0,
10961                       pvInfoList[i].depth,
10962                       buf );
10963
10964             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10965
10966             /* Print score/depth */
10967             blank = linelen > 0 && movelen > 0;
10968             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10969                 fprintf(f, "\n");
10970                 linelen = 0;
10971                 blank = 0;
10972             }
10973             if (blank) {
10974                 fprintf(f, " ");
10975                 linelen++;
10976             }
10977             fprintf(f, "%s", move_buffer);
10978             linelen += movelen;
10979         }
10980
10981         i++;
10982     }
10983
10984     /* Start a new line */
10985     if (linelen > 0) fprintf(f, "\n");
10986
10987     /* Print comments after last move */
10988     if (commentList[i] != NULL) {
10989         fprintf(f, "%s\n", commentList[i]);
10990     }
10991
10992     /* Print result */
10993     if (gameInfo.resultDetails != NULL &&
10994         gameInfo.resultDetails[0] != NULLCHAR) {
10995         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10996                 PGNResult(gameInfo.result));
10997     } else {
10998         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10999     }
11000
11001     fclose(f);
11002     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11003     return TRUE;
11004 }
11005
11006 /* Save game in old style and close the file */
11007 int
11008 SaveGameOldStyle(f)
11009      FILE *f;
11010 {
11011     int i, offset;
11012     time_t tm;
11013
11014     tm = time((time_t *) NULL);
11015
11016     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11017     PrintOpponents(f);
11018
11019     if (backwardMostMove > 0 || startedFromSetupPosition) {
11020         fprintf(f, "\n[--------------\n");
11021         PrintPosition(f, backwardMostMove);
11022         fprintf(f, "--------------]\n");
11023     } else {
11024         fprintf(f, "\n");
11025     }
11026
11027     i = backwardMostMove;
11028     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11029
11030     while (i < forwardMostMove) {
11031         if (commentList[i] != NULL) {
11032             fprintf(f, "[%s]\n", commentList[i]);
11033         }
11034
11035         if ((i % 2) == 1) {
11036             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11037             i++;
11038         } else {
11039             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11040             i++;
11041             if (commentList[i] != NULL) {
11042                 fprintf(f, "\n");
11043                 continue;
11044             }
11045             if (i >= forwardMostMove) {
11046                 fprintf(f, "\n");
11047                 break;
11048             }
11049             fprintf(f, "%s\n", parseList[i]);
11050             i++;
11051         }
11052     }
11053
11054     if (commentList[i] != NULL) {
11055         fprintf(f, "[%s]\n", commentList[i]);
11056     }
11057
11058     /* This isn't really the old style, but it's close enough */
11059     if (gameInfo.resultDetails != NULL &&
11060         gameInfo.resultDetails[0] != NULLCHAR) {
11061         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11062                 gameInfo.resultDetails);
11063     } else {
11064         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11065     }
11066
11067     fclose(f);
11068     return TRUE;
11069 }
11070
11071 /* Save the current game to open file f and close the file */
11072 int
11073 SaveGame(f, dummy, dummy2)
11074      FILE *f;
11075      int dummy;
11076      char *dummy2;
11077 {
11078     if (gameMode == EditPosition) EditPositionDone(TRUE);
11079     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11080     if (appData.oldSaveStyle)
11081       return SaveGameOldStyle(f);
11082     else
11083       return SaveGamePGN(f);
11084 }
11085
11086 /* Save the current position to the given file */
11087 int
11088 SavePositionToFile(filename)
11089      char *filename;
11090 {
11091     FILE *f;
11092     char buf[MSG_SIZ];
11093
11094     if (strcmp(filename, "-") == 0) {
11095         return SavePosition(stdout, 0, NULL);
11096     } else {
11097         f = fopen(filename, "a");
11098         if (f == NULL) {
11099             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11100             DisplayError(buf, errno);
11101             return FALSE;
11102         } else {
11103             SavePosition(f, 0, NULL);
11104             return TRUE;
11105         }
11106     }
11107 }
11108
11109 /* Save the current position to the given open file and close the file */
11110 int
11111 SavePosition(f, dummy, dummy2)
11112      FILE *f;
11113      int dummy;
11114      char *dummy2;
11115 {
11116     time_t tm;
11117     char *fen;
11118
11119     if (gameMode == EditPosition) EditPositionDone(TRUE);
11120     if (appData.oldSaveStyle) {
11121         tm = time((time_t *) NULL);
11122
11123         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11124         PrintOpponents(f);
11125         fprintf(f, "[--------------\n");
11126         PrintPosition(f, currentMove);
11127         fprintf(f, "--------------]\n");
11128     } else {
11129         fen = PositionToFEN(currentMove, NULL);
11130         fprintf(f, "%s\n", fen);
11131         free(fen);
11132     }
11133     fclose(f);
11134     return TRUE;
11135 }
11136
11137 void
11138 ReloadCmailMsgEvent(unregister)
11139      int unregister;
11140 {
11141 #if !WIN32
11142     static char *inFilename = NULL;
11143     static char *outFilename;
11144     int i;
11145     struct stat inbuf, outbuf;
11146     int status;
11147
11148     /* Any registered moves are unregistered if unregister is set, */
11149     /* i.e. invoked by the signal handler */
11150     if (unregister) {
11151         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11152             cmailMoveRegistered[i] = FALSE;
11153             if (cmailCommentList[i] != NULL) {
11154                 free(cmailCommentList[i]);
11155                 cmailCommentList[i] = NULL;
11156             }
11157         }
11158         nCmailMovesRegistered = 0;
11159     }
11160
11161     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11162         cmailResult[i] = CMAIL_NOT_RESULT;
11163     }
11164     nCmailResults = 0;
11165
11166     if (inFilename == NULL) {
11167         /* Because the filenames are static they only get malloced once  */
11168         /* and they never get freed                                      */
11169         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11170         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11171
11172         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11173         sprintf(outFilename, "%s.out", appData.cmailGameName);
11174     }
11175
11176     status = stat(outFilename, &outbuf);
11177     if (status < 0) {
11178         cmailMailedMove = FALSE;
11179     } else {
11180         status = stat(inFilename, &inbuf);
11181         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11182     }
11183
11184     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11185        counts the games, notes how each one terminated, etc.
11186
11187        It would be nice to remove this kludge and instead gather all
11188        the information while building the game list.  (And to keep it
11189        in the game list nodes instead of having a bunch of fixed-size
11190        parallel arrays.)  Note this will require getting each game's
11191        termination from the PGN tags, as the game list builder does
11192        not process the game moves.  --mann
11193        */
11194     cmailMsgLoaded = TRUE;
11195     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11196
11197     /* Load first game in the file or popup game menu */
11198     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11199
11200 #endif /* !WIN32 */
11201     return;
11202 }
11203
11204 int
11205 RegisterMove()
11206 {
11207     FILE *f;
11208     char string[MSG_SIZ];
11209
11210     if (   cmailMailedMove
11211         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11212         return TRUE;            /* Allow free viewing  */
11213     }
11214
11215     /* Unregister move to ensure that we don't leave RegisterMove        */
11216     /* with the move registered when the conditions for registering no   */
11217     /* longer hold                                                       */
11218     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11219         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11220         nCmailMovesRegistered --;
11221
11222         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11223           {
11224               free(cmailCommentList[lastLoadGameNumber - 1]);
11225               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11226           }
11227     }
11228
11229     if (cmailOldMove == -1) {
11230         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11231         return FALSE;
11232     }
11233
11234     if (currentMove > cmailOldMove + 1) {
11235         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11236         return FALSE;
11237     }
11238
11239     if (currentMove < cmailOldMove) {
11240         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11241         return FALSE;
11242     }
11243
11244     if (forwardMostMove > currentMove) {
11245         /* Silently truncate extra moves */
11246         TruncateGame();
11247     }
11248
11249     if (   (currentMove == cmailOldMove + 1)
11250         || (   (currentMove == cmailOldMove)
11251             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11252                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11253         if (gameInfo.result != GameUnfinished) {
11254             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11255         }
11256
11257         if (commentList[currentMove] != NULL) {
11258             cmailCommentList[lastLoadGameNumber - 1]
11259               = StrSave(commentList[currentMove]);
11260         }
11261         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11262
11263         if (appData.debugMode)
11264           fprintf(debugFP, "Saving %s for game %d\n",
11265                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11266
11267         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11268
11269         f = fopen(string, "w");
11270         if (appData.oldSaveStyle) {
11271             SaveGameOldStyle(f); /* also closes the file */
11272
11273             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11274             f = fopen(string, "w");
11275             SavePosition(f, 0, NULL); /* also closes the file */
11276         } else {
11277             fprintf(f, "{--------------\n");
11278             PrintPosition(f, currentMove);
11279             fprintf(f, "--------------}\n\n");
11280
11281             SaveGame(f, 0, NULL); /* also closes the file*/
11282         }
11283
11284         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11285         nCmailMovesRegistered ++;
11286     } else if (nCmailGames == 1) {
11287         DisplayError(_("You have not made a move yet"), 0);
11288         return FALSE;
11289     }
11290
11291     return TRUE;
11292 }
11293
11294 void
11295 MailMoveEvent()
11296 {
11297 #if !WIN32
11298     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11299     FILE *commandOutput;
11300     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11301     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11302     int nBuffers;
11303     int i;
11304     int archived;
11305     char *arcDir;
11306
11307     if (! cmailMsgLoaded) {
11308         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11309         return;
11310     }
11311
11312     if (nCmailGames == nCmailResults) {
11313         DisplayError(_("No unfinished games"), 0);
11314         return;
11315     }
11316
11317 #if CMAIL_PROHIBIT_REMAIL
11318     if (cmailMailedMove) {
11319       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);
11320         DisplayError(msg, 0);
11321         return;
11322     }
11323 #endif
11324
11325     if (! (cmailMailedMove || RegisterMove())) return;
11326
11327     if (   cmailMailedMove
11328         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11329       snprintf(string, MSG_SIZ, partCommandString,
11330                appData.debugMode ? " -v" : "", appData.cmailGameName);
11331         commandOutput = popen(string, "r");
11332
11333         if (commandOutput == NULL) {
11334             DisplayError(_("Failed to invoke cmail"), 0);
11335         } else {
11336             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11337                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11338             }
11339             if (nBuffers > 1) {
11340                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11341                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11342                 nBytes = MSG_SIZ - 1;
11343             } else {
11344                 (void) memcpy(msg, buffer, nBytes);
11345             }
11346             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11347
11348             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11349                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11350
11351                 archived = TRUE;
11352                 for (i = 0; i < nCmailGames; i ++) {
11353                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11354                         archived = FALSE;
11355                     }
11356                 }
11357                 if (   archived
11358                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11359                         != NULL)) {
11360                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11361                            arcDir,
11362                            appData.cmailGameName,
11363                            gameInfo.date);
11364                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11365                     cmailMsgLoaded = FALSE;
11366                 }
11367             }
11368
11369             DisplayInformation(msg);
11370             pclose(commandOutput);
11371         }
11372     } else {
11373         if ((*cmailMsg) != '\0') {
11374             DisplayInformation(cmailMsg);
11375         }
11376     }
11377
11378     return;
11379 #endif /* !WIN32 */
11380 }
11381
11382 char *
11383 CmailMsg()
11384 {
11385 #if WIN32
11386     return NULL;
11387 #else
11388     int  prependComma = 0;
11389     char number[5];
11390     char string[MSG_SIZ];       /* Space for game-list */
11391     int  i;
11392
11393     if (!cmailMsgLoaded) return "";
11394
11395     if (cmailMailedMove) {
11396       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11397     } else {
11398         /* Create a list of games left */
11399       snprintf(string, MSG_SIZ, "[");
11400         for (i = 0; i < nCmailGames; i ++) {
11401             if (! (   cmailMoveRegistered[i]
11402                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11403                 if (prependComma) {
11404                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11405                 } else {
11406                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11407                     prependComma = 1;
11408                 }
11409
11410                 strcat(string, number);
11411             }
11412         }
11413         strcat(string, "]");
11414
11415         if (nCmailMovesRegistered + nCmailResults == 0) {
11416             switch (nCmailGames) {
11417               case 1:
11418                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11419                 break;
11420
11421               case 2:
11422                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11423                 break;
11424
11425               default:
11426                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11427                          nCmailGames);
11428                 break;
11429             }
11430         } else {
11431             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11432               case 1:
11433                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11434                          string);
11435                 break;
11436
11437               case 0:
11438                 if (nCmailResults == nCmailGames) {
11439                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11440                 } else {
11441                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11442                 }
11443                 break;
11444
11445               default:
11446                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11447                          string);
11448             }
11449         }
11450     }
11451     return cmailMsg;
11452 #endif /* WIN32 */
11453 }
11454
11455 void
11456 ResetGameEvent()
11457 {
11458     if (gameMode == Training)
11459       SetTrainingModeOff();
11460
11461     Reset(TRUE, TRUE);
11462     cmailMsgLoaded = FALSE;
11463     if (appData.icsActive) {
11464       SendToICS(ics_prefix);
11465       SendToICS("refresh\n");
11466     }
11467 }
11468
11469 void
11470 ExitEvent(status)
11471      int status;
11472 {
11473     exiting++;
11474     if (exiting > 2) {
11475       /* Give up on clean exit */
11476       exit(status);
11477     }
11478     if (exiting > 1) {
11479       /* Keep trying for clean exit */
11480       return;
11481     }
11482
11483     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11484
11485     if (telnetISR != NULL) {
11486       RemoveInputSource(telnetISR);
11487     }
11488     if (icsPR != NoProc) {
11489       DestroyChildProcess(icsPR, TRUE);
11490     }
11491
11492     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11493     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11494
11495     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11496     /* make sure this other one finishes before killing it!                  */
11497     if(endingGame) { int count = 0;
11498         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11499         while(endingGame && count++ < 10) DoSleep(1);
11500         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11501     }
11502
11503     /* Kill off chess programs */
11504     if (first.pr != NoProc) {
11505         ExitAnalyzeMode();
11506
11507         DoSleep( appData.delayBeforeQuit );
11508         SendToProgram("quit\n", &first);
11509         DoSleep( appData.delayAfterQuit );
11510         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11511     }
11512     if (second.pr != NoProc) {
11513         DoSleep( appData.delayBeforeQuit );
11514         SendToProgram("quit\n", &second);
11515         DoSleep( appData.delayAfterQuit );
11516         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11517     }
11518     if (first.isr != NULL) {
11519         RemoveInputSource(first.isr);
11520     }
11521     if (second.isr != NULL) {
11522         RemoveInputSource(second.isr);
11523     }
11524
11525     ShutDownFrontEnd();
11526     exit(status);
11527 }
11528
11529 void
11530 PauseEvent()
11531 {
11532     if (appData.debugMode)
11533         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11534     if (pausing) {
11535         pausing = FALSE;
11536         ModeHighlight();
11537         if (gameMode == MachinePlaysWhite ||
11538             gameMode == MachinePlaysBlack) {
11539             StartClocks();
11540         } else {
11541             DisplayBothClocks();
11542         }
11543         if (gameMode == PlayFromGameFile) {
11544             if (appData.timeDelay >= 0)
11545                 AutoPlayGameLoop();
11546         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11547             Reset(FALSE, TRUE);
11548             SendToICS(ics_prefix);
11549             SendToICS("refresh\n");
11550         } else if (currentMove < forwardMostMove) {
11551             ForwardInner(forwardMostMove);
11552         }
11553         pauseExamInvalid = FALSE;
11554     } else {
11555         switch (gameMode) {
11556           default:
11557             return;
11558           case IcsExamining:
11559             pauseExamForwardMostMove = forwardMostMove;
11560             pauseExamInvalid = FALSE;
11561             /* fall through */
11562           case IcsObserving:
11563           case IcsPlayingWhite:
11564           case IcsPlayingBlack:
11565             pausing = TRUE;
11566             ModeHighlight();
11567             return;
11568           case PlayFromGameFile:
11569             (void) StopLoadGameTimer();
11570             pausing = TRUE;
11571             ModeHighlight();
11572             break;
11573           case BeginningOfGame:
11574             if (appData.icsActive) return;
11575             /* else fall through */
11576           case MachinePlaysWhite:
11577           case MachinePlaysBlack:
11578           case TwoMachinesPlay:
11579             if (forwardMostMove == 0)
11580               return;           /* don't pause if no one has moved */
11581             if ((gameMode == MachinePlaysWhite &&
11582                  !WhiteOnMove(forwardMostMove)) ||
11583                 (gameMode == MachinePlaysBlack &&
11584                  WhiteOnMove(forwardMostMove))) {
11585                 StopClocks();
11586             }
11587             pausing = TRUE;
11588             ModeHighlight();
11589             break;
11590         }
11591     }
11592 }
11593
11594 void
11595 EditCommentEvent()
11596 {
11597     char title[MSG_SIZ];
11598
11599     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11600       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11601     } else {
11602       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11603                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11604                parseList[currentMove - 1]);
11605     }
11606
11607     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11608 }
11609
11610
11611 void
11612 EditTagsEvent()
11613 {
11614     char *tags = PGNTags(&gameInfo);
11615     EditTagsPopUp(tags);
11616     free(tags);
11617 }
11618
11619 void
11620 AnalyzeModeEvent()
11621 {
11622     if (appData.noChessProgram || gameMode == AnalyzeMode)
11623       return;
11624
11625     if (gameMode != AnalyzeFile) {
11626         if (!appData.icsEngineAnalyze) {
11627                EditGameEvent();
11628                if (gameMode != EditGame) return;
11629         }
11630         ResurrectChessProgram();
11631         SendToProgram("analyze\n", &first);
11632         first.analyzing = TRUE;
11633         /*first.maybeThinking = TRUE;*/
11634         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11635         EngineOutputPopUp();
11636     }
11637     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11638     pausing = FALSE;
11639     ModeHighlight();
11640     SetGameInfo();
11641
11642     StartAnalysisClock();
11643     GetTimeMark(&lastNodeCountTime);
11644     lastNodeCount = 0;
11645 }
11646
11647 void
11648 AnalyzeFileEvent()
11649 {
11650     if (appData.noChessProgram || gameMode == AnalyzeFile)
11651       return;
11652
11653     if (gameMode != AnalyzeMode) {
11654         EditGameEvent();
11655         if (gameMode != EditGame) return;
11656         ResurrectChessProgram();
11657         SendToProgram("analyze\n", &first);
11658         first.analyzing = TRUE;
11659         /*first.maybeThinking = TRUE;*/
11660         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11661         EngineOutputPopUp();
11662     }
11663     gameMode = AnalyzeFile;
11664     pausing = FALSE;
11665     ModeHighlight();
11666     SetGameInfo();
11667
11668     StartAnalysisClock();
11669     GetTimeMark(&lastNodeCountTime);
11670     lastNodeCount = 0;
11671 }
11672
11673 void
11674 MachineWhiteEvent()
11675 {
11676     char buf[MSG_SIZ];
11677     char *bookHit = NULL;
11678
11679     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11680       return;
11681
11682
11683     if (gameMode == PlayFromGameFile ||
11684         gameMode == TwoMachinesPlay  ||
11685         gameMode == Training         ||
11686         gameMode == AnalyzeMode      ||
11687         gameMode == EndOfGame)
11688         EditGameEvent();
11689
11690     if (gameMode == EditPosition)
11691         EditPositionDone(TRUE);
11692
11693     if (!WhiteOnMove(currentMove)) {
11694         DisplayError(_("It is not White's turn"), 0);
11695         return;
11696     }
11697
11698     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11699       ExitAnalyzeMode();
11700
11701     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11702         gameMode == AnalyzeFile)
11703         TruncateGame();
11704
11705     ResurrectChessProgram();    /* in case it isn't running */
11706     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11707         gameMode = MachinePlaysWhite;
11708         ResetClocks();
11709     } else
11710     gameMode = MachinePlaysWhite;
11711     pausing = FALSE;
11712     ModeHighlight();
11713     SetGameInfo();
11714     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11715     DisplayTitle(buf);
11716     if (first.sendName) {
11717       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11718       SendToProgram(buf, &first);
11719     }
11720     if (first.sendTime) {
11721       if (first.useColors) {
11722         SendToProgram("black\n", &first); /*gnu kludge*/
11723       }
11724       SendTimeRemaining(&first, TRUE);
11725     }
11726     if (first.useColors) {
11727       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11728     }
11729     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11730     SetMachineThinkingEnables();
11731     first.maybeThinking = TRUE;
11732     StartClocks();
11733     firstMove = FALSE;
11734
11735     if (appData.autoFlipView && !flipView) {
11736       flipView = !flipView;
11737       DrawPosition(FALSE, NULL);
11738       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11739     }
11740
11741     if(bookHit) { // [HGM] book: simulate book reply
11742         static char bookMove[MSG_SIZ]; // a bit generous?
11743
11744         programStats.nodes = programStats.depth = programStats.time =
11745         programStats.score = programStats.got_only_move = 0;
11746         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11747
11748         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11749         strcat(bookMove, bookHit);
11750         HandleMachineMove(bookMove, &first);
11751     }
11752 }
11753
11754 void
11755 MachineBlackEvent()
11756 {
11757   char buf[MSG_SIZ];
11758   char *bookHit = NULL;
11759
11760     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11761         return;
11762
11763
11764     if (gameMode == PlayFromGameFile ||
11765         gameMode == TwoMachinesPlay  ||
11766         gameMode == Training         ||
11767         gameMode == AnalyzeMode      ||
11768         gameMode == EndOfGame)
11769         EditGameEvent();
11770
11771     if (gameMode == EditPosition)
11772         EditPositionDone(TRUE);
11773
11774     if (WhiteOnMove(currentMove)) {
11775         DisplayError(_("It is not Black's turn"), 0);
11776         return;
11777     }
11778
11779     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11780       ExitAnalyzeMode();
11781
11782     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11783         gameMode == AnalyzeFile)
11784         TruncateGame();
11785
11786     ResurrectChessProgram();    /* in case it isn't running */
11787     gameMode = MachinePlaysBlack;
11788     pausing = FALSE;
11789     ModeHighlight();
11790     SetGameInfo();
11791     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11792     DisplayTitle(buf);
11793     if (first.sendName) {
11794       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11795       SendToProgram(buf, &first);
11796     }
11797     if (first.sendTime) {
11798       if (first.useColors) {
11799         SendToProgram("white\n", &first); /*gnu kludge*/
11800       }
11801       SendTimeRemaining(&first, FALSE);
11802     }
11803     if (first.useColors) {
11804       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11805     }
11806     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11807     SetMachineThinkingEnables();
11808     first.maybeThinking = TRUE;
11809     StartClocks();
11810
11811     if (appData.autoFlipView && flipView) {
11812       flipView = !flipView;
11813       DrawPosition(FALSE, NULL);
11814       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11815     }
11816     if(bookHit) { // [HGM] book: simulate book reply
11817         static char bookMove[MSG_SIZ]; // a bit generous?
11818
11819         programStats.nodes = programStats.depth = programStats.time =
11820         programStats.score = programStats.got_only_move = 0;
11821         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11822
11823         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11824         strcat(bookMove, bookHit);
11825         HandleMachineMove(bookMove, &first);
11826     }
11827 }
11828
11829
11830 void
11831 DisplayTwoMachinesTitle()
11832 {
11833     char buf[MSG_SIZ];
11834     if (appData.matchGames > 0) {
11835         if (first.twoMachinesColor[0] == 'w') {
11836           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11837                    gameInfo.white, gameInfo.black,
11838                    first.matchWins, second.matchWins,
11839                    matchGame - 1 - (first.matchWins + second.matchWins));
11840         } else {
11841           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11842                    gameInfo.white, gameInfo.black,
11843                    second.matchWins, first.matchWins,
11844                    matchGame - 1 - (first.matchWins + second.matchWins));
11845         }
11846     } else {
11847       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11848     }
11849     DisplayTitle(buf);
11850 }
11851
11852 void
11853 TwoMachinesEvent P((void))
11854 {
11855     int i;
11856     char buf[MSG_SIZ];
11857     ChessProgramState *onmove;
11858     char *bookHit = NULL;
11859
11860     if (appData.noChessProgram) return;
11861
11862     switch (gameMode) {
11863       case TwoMachinesPlay:
11864         return;
11865       case MachinePlaysWhite:
11866       case MachinePlaysBlack:
11867         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11868             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11869             return;
11870         }
11871         /* fall through */
11872       case BeginningOfGame:
11873       case PlayFromGameFile:
11874       case EndOfGame:
11875         EditGameEvent();
11876         if (gameMode != EditGame) return;
11877         break;
11878       case EditPosition:
11879         EditPositionDone(TRUE);
11880         break;
11881       case AnalyzeMode:
11882       case AnalyzeFile:
11883         ExitAnalyzeMode();
11884         break;
11885       case EditGame:
11886       default:
11887         break;
11888     }
11889
11890 //    forwardMostMove = currentMove;
11891     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11892     ResurrectChessProgram();    /* in case first program isn't running */
11893
11894     if (second.pr == NULL) {
11895         StartChessProgram(&second);
11896         if (second.protocolVersion == 1) {
11897           TwoMachinesEventIfReady();
11898         } else {
11899           /* kludge: allow timeout for initial "feature" command */
11900           FreezeUI();
11901           DisplayMessage("", _("Starting second chess program"));
11902           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11903         }
11904         return;
11905     }
11906     DisplayMessage("", "");
11907     InitChessProgram(&second, FALSE);
11908     SendToProgram("force\n", &second);
11909     if (startedFromSetupPosition) {
11910         SendBoard(&second, backwardMostMove);
11911     if (appData.debugMode) {
11912         fprintf(debugFP, "Two Machines\n");
11913     }
11914     }
11915     for (i = backwardMostMove; i < forwardMostMove; i++) {
11916         SendMoveToProgram(i, &second);
11917     }
11918
11919     gameMode = TwoMachinesPlay;
11920     pausing = FALSE;
11921     ModeHighlight();
11922     SetGameInfo();
11923     DisplayTwoMachinesTitle();
11924     firstMove = TRUE;
11925     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11926         onmove = &first;
11927     } else {
11928         onmove = &second;
11929     }
11930
11931     SendToProgram(first.computerString, &first);
11932     if (first.sendName) {
11933       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11934       SendToProgram(buf, &first);
11935     }
11936     SendToProgram(second.computerString, &second);
11937     if (second.sendName) {
11938       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11939       SendToProgram(buf, &second);
11940     }
11941
11942     ResetClocks();
11943     if (!first.sendTime || !second.sendTime) {
11944         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11945         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11946     }
11947     if (onmove->sendTime) {
11948       if (onmove->useColors) {
11949         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11950       }
11951       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11952     }
11953     if (onmove->useColors) {
11954       SendToProgram(onmove->twoMachinesColor, onmove);
11955     }
11956     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11957 //    SendToProgram("go\n", onmove);
11958     onmove->maybeThinking = TRUE;
11959     SetMachineThinkingEnables();
11960
11961     StartClocks();
11962
11963     if(bookHit) { // [HGM] book: simulate book reply
11964         static char bookMove[MSG_SIZ]; // a bit generous?
11965
11966         programStats.nodes = programStats.depth = programStats.time =
11967         programStats.score = programStats.got_only_move = 0;
11968         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11969
11970         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11971         strcat(bookMove, bookHit);
11972         savedMessage = bookMove; // args for deferred call
11973         savedState = onmove;
11974         ScheduleDelayedEvent(DeferredBookMove, 1);
11975     }
11976 }
11977
11978 void
11979 TrainingEvent()
11980 {
11981     if (gameMode == Training) {
11982       SetTrainingModeOff();
11983       gameMode = PlayFromGameFile;
11984       DisplayMessage("", _("Training mode off"));
11985     } else {
11986       gameMode = Training;
11987       animateTraining = appData.animate;
11988
11989       /* make sure we are not already at the end of the game */
11990       if (currentMove < forwardMostMove) {
11991         SetTrainingModeOn();
11992         DisplayMessage("", _("Training mode on"));
11993       } else {
11994         gameMode = PlayFromGameFile;
11995         DisplayError(_("Already at end of game"), 0);
11996       }
11997     }
11998     ModeHighlight();
11999 }
12000
12001 void
12002 IcsClientEvent()
12003 {
12004     if (!appData.icsActive) return;
12005     switch (gameMode) {
12006       case IcsPlayingWhite:
12007       case IcsPlayingBlack:
12008       case IcsObserving:
12009       case IcsIdle:
12010       case BeginningOfGame:
12011       case IcsExamining:
12012         return;
12013
12014       case EditGame:
12015         break;
12016
12017       case EditPosition:
12018         EditPositionDone(TRUE);
12019         break;
12020
12021       case AnalyzeMode:
12022       case AnalyzeFile:
12023         ExitAnalyzeMode();
12024         break;
12025
12026       default:
12027         EditGameEvent();
12028         break;
12029     }
12030
12031     gameMode = IcsIdle;
12032     ModeHighlight();
12033     return;
12034 }
12035
12036
12037 void
12038 EditGameEvent()
12039 {
12040     int i;
12041
12042     switch (gameMode) {
12043       case Training:
12044         SetTrainingModeOff();
12045         break;
12046       case MachinePlaysWhite:
12047       case MachinePlaysBlack:
12048       case BeginningOfGame:
12049         SendToProgram("force\n", &first);
12050         SetUserThinkingEnables();
12051         break;
12052       case PlayFromGameFile:
12053         (void) StopLoadGameTimer();
12054         if (gameFileFP != NULL) {
12055             gameFileFP = NULL;
12056         }
12057         break;
12058       case EditPosition:
12059         EditPositionDone(TRUE);
12060         break;
12061       case AnalyzeMode:
12062       case AnalyzeFile:
12063         ExitAnalyzeMode();
12064         SendToProgram("force\n", &first);
12065         break;
12066       case TwoMachinesPlay:
12067         GameEnds(EndOfFile, NULL, GE_PLAYER);
12068         ResurrectChessProgram();
12069         SetUserThinkingEnables();
12070         break;
12071       case EndOfGame:
12072         ResurrectChessProgram();
12073         break;
12074       case IcsPlayingBlack:
12075       case IcsPlayingWhite:
12076         DisplayError(_("Warning: You are still playing a game"), 0);
12077         break;
12078       case IcsObserving:
12079         DisplayError(_("Warning: You are still observing a game"), 0);
12080         break;
12081       case IcsExamining:
12082         DisplayError(_("Warning: You are still examining a game"), 0);
12083         break;
12084       case IcsIdle:
12085         break;
12086       case EditGame:
12087       default:
12088         return;
12089     }
12090
12091     pausing = FALSE;
12092     StopClocks();
12093     first.offeredDraw = second.offeredDraw = 0;
12094
12095     if (gameMode == PlayFromGameFile) {
12096         whiteTimeRemaining = timeRemaining[0][currentMove];
12097         blackTimeRemaining = timeRemaining[1][currentMove];
12098         DisplayTitle("");
12099     }
12100
12101     if (gameMode == MachinePlaysWhite ||
12102         gameMode == MachinePlaysBlack ||
12103         gameMode == TwoMachinesPlay ||
12104         gameMode == EndOfGame) {
12105         i = forwardMostMove;
12106         while (i > currentMove) {
12107             SendToProgram("undo\n", &first);
12108             i--;
12109         }
12110         whiteTimeRemaining = timeRemaining[0][currentMove];
12111         blackTimeRemaining = timeRemaining[1][currentMove];
12112         DisplayBothClocks();
12113         if (whiteFlag || blackFlag) {
12114             whiteFlag = blackFlag = 0;
12115         }
12116         DisplayTitle("");
12117     }
12118
12119     gameMode = EditGame;
12120     ModeHighlight();
12121     SetGameInfo();
12122 }
12123
12124
12125 void
12126 EditPositionEvent()
12127 {
12128     if (gameMode == EditPosition) {
12129         EditGameEvent();
12130         return;
12131     }
12132
12133     EditGameEvent();
12134     if (gameMode != EditGame) return;
12135
12136     gameMode = EditPosition;
12137     ModeHighlight();
12138     SetGameInfo();
12139     if (currentMove > 0)
12140       CopyBoard(boards[0], boards[currentMove]);
12141
12142     blackPlaysFirst = !WhiteOnMove(currentMove);
12143     ResetClocks();
12144     currentMove = forwardMostMove = backwardMostMove = 0;
12145     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12146     DisplayMove(-1);
12147 }
12148
12149 void
12150 ExitAnalyzeMode()
12151 {
12152     /* [DM] icsEngineAnalyze - possible call from other functions */
12153     if (appData.icsEngineAnalyze) {
12154         appData.icsEngineAnalyze = FALSE;
12155
12156         DisplayMessage("",_("Close ICS engine analyze..."));
12157     }
12158     if (first.analysisSupport && first.analyzing) {
12159       SendToProgram("exit\n", &first);
12160       first.analyzing = FALSE;
12161     }
12162     thinkOutput[0] = NULLCHAR;
12163 }
12164
12165 void
12166 EditPositionDone(Boolean fakeRights)
12167 {
12168     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12169
12170     startedFromSetupPosition = TRUE;
12171     InitChessProgram(&first, FALSE);
12172     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12173       boards[0][EP_STATUS] = EP_NONE;
12174       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12175     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12176         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12177         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12178       } else boards[0][CASTLING][2] = NoRights;
12179     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12180         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12181         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12182       } else boards[0][CASTLING][5] = NoRights;
12183     }
12184     SendToProgram("force\n", &first);
12185     if (blackPlaysFirst) {
12186         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12187         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12188         currentMove = forwardMostMove = backwardMostMove = 1;
12189         CopyBoard(boards[1], boards[0]);
12190     } else {
12191         currentMove = forwardMostMove = backwardMostMove = 0;
12192     }
12193     SendBoard(&first, forwardMostMove);
12194     if (appData.debugMode) {
12195         fprintf(debugFP, "EditPosDone\n");
12196     }
12197     DisplayTitle("");
12198     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12199     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12200     gameMode = EditGame;
12201     ModeHighlight();
12202     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12203     ClearHighlights(); /* [AS] */
12204 }
12205
12206 /* Pause for `ms' milliseconds */
12207 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12208 void
12209 TimeDelay(ms)
12210      long ms;
12211 {
12212     TimeMark m1, m2;
12213
12214     GetTimeMark(&m1);
12215     do {
12216         GetTimeMark(&m2);
12217     } while (SubtractTimeMarks(&m2, &m1) < ms);
12218 }
12219
12220 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12221 void
12222 SendMultiLineToICS(buf)
12223      char *buf;
12224 {
12225     char temp[MSG_SIZ+1], *p;
12226     int len;
12227
12228     len = strlen(buf);
12229     if (len > MSG_SIZ)
12230       len = MSG_SIZ;
12231
12232     strncpy(temp, buf, len);
12233     temp[len] = 0;
12234
12235     p = temp;
12236     while (*p) {
12237         if (*p == '\n' || *p == '\r')
12238           *p = ' ';
12239         ++p;
12240     }
12241
12242     strcat(temp, "\n");
12243     SendToICS(temp);
12244     SendToPlayer(temp, strlen(temp));
12245 }
12246
12247 void
12248 SetWhiteToPlayEvent()
12249 {
12250     if (gameMode == EditPosition) {
12251         blackPlaysFirst = FALSE;
12252         DisplayBothClocks();    /* works because currentMove is 0 */
12253     } else if (gameMode == IcsExamining) {
12254         SendToICS(ics_prefix);
12255         SendToICS("tomove white\n");
12256     }
12257 }
12258
12259 void
12260 SetBlackToPlayEvent()
12261 {
12262     if (gameMode == EditPosition) {
12263         blackPlaysFirst = TRUE;
12264         currentMove = 1;        /* kludge */
12265         DisplayBothClocks();
12266         currentMove = 0;
12267     } else if (gameMode == IcsExamining) {
12268         SendToICS(ics_prefix);
12269         SendToICS("tomove black\n");
12270     }
12271 }
12272
12273 void
12274 EditPositionMenuEvent(selection, x, y)
12275      ChessSquare selection;
12276      int x, y;
12277 {
12278     char buf[MSG_SIZ];
12279     ChessSquare piece = boards[0][y][x];
12280
12281     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12282
12283     switch (selection) {
12284       case ClearBoard:
12285         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12286             SendToICS(ics_prefix);
12287             SendToICS("bsetup clear\n");
12288         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12289             SendToICS(ics_prefix);
12290             SendToICS("clearboard\n");
12291         } else {
12292             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12293                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12294                 for (y = 0; y < BOARD_HEIGHT; y++) {
12295                     if (gameMode == IcsExamining) {
12296                         if (boards[currentMove][y][x] != EmptySquare) {
12297                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12298                                     AAA + x, ONE + y);
12299                             SendToICS(buf);
12300                         }
12301                     } else {
12302                         boards[0][y][x] = p;
12303                     }
12304                 }
12305             }
12306         }
12307         if (gameMode == EditPosition) {
12308             DrawPosition(FALSE, boards[0]);
12309         }
12310         break;
12311
12312       case WhitePlay:
12313         SetWhiteToPlayEvent();
12314         break;
12315
12316       case BlackPlay:
12317         SetBlackToPlayEvent();
12318         break;
12319
12320       case EmptySquare:
12321         if (gameMode == IcsExamining) {
12322             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12323             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12324             SendToICS(buf);
12325         } else {
12326             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12327                 if(x == BOARD_LEFT-2) {
12328                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12329                     boards[0][y][1] = 0;
12330                 } else
12331                 if(x == BOARD_RGHT+1) {
12332                     if(y >= gameInfo.holdingsSize) break;
12333                     boards[0][y][BOARD_WIDTH-2] = 0;
12334                 } else break;
12335             }
12336             boards[0][y][x] = EmptySquare;
12337             DrawPosition(FALSE, boards[0]);
12338         }
12339         break;
12340
12341       case PromotePiece:
12342         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12343            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12344             selection = (ChessSquare) (PROMOTED piece);
12345         } else if(piece == EmptySquare) selection = WhiteSilver;
12346         else selection = (ChessSquare)((int)piece - 1);
12347         goto defaultlabel;
12348
12349       case DemotePiece:
12350         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12351            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12352             selection = (ChessSquare) (DEMOTED piece);
12353         } else if(piece == EmptySquare) selection = BlackSilver;
12354         else selection = (ChessSquare)((int)piece + 1);
12355         goto defaultlabel;
12356
12357       case WhiteQueen:
12358       case BlackQueen:
12359         if(gameInfo.variant == VariantShatranj ||
12360            gameInfo.variant == VariantXiangqi  ||
12361            gameInfo.variant == VariantCourier  ||
12362            gameInfo.variant == VariantMakruk     )
12363             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12364         goto defaultlabel;
12365
12366       case WhiteKing:
12367       case BlackKing:
12368         if(gameInfo.variant == VariantXiangqi)
12369             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12370         if(gameInfo.variant == VariantKnightmate)
12371             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12372       default:
12373         defaultlabel:
12374         if (gameMode == IcsExamining) {
12375             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12376             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12377                      PieceToChar(selection), AAA + x, ONE + y);
12378             SendToICS(buf);
12379         } else {
12380             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12381                 int n;
12382                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12383                     n = PieceToNumber(selection - BlackPawn);
12384                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12385                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12386                     boards[0][BOARD_HEIGHT-1-n][1]++;
12387                 } else
12388                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12389                     n = PieceToNumber(selection);
12390                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12391                     boards[0][n][BOARD_WIDTH-1] = selection;
12392                     boards[0][n][BOARD_WIDTH-2]++;
12393                 }
12394             } else
12395             boards[0][y][x] = selection;
12396             DrawPosition(TRUE, boards[0]);
12397         }
12398         break;
12399     }
12400 }
12401
12402
12403 void
12404 DropMenuEvent(selection, x, y)
12405      ChessSquare selection;
12406      int x, y;
12407 {
12408     ChessMove moveType;
12409
12410     switch (gameMode) {
12411       case IcsPlayingWhite:
12412       case MachinePlaysBlack:
12413         if (!WhiteOnMove(currentMove)) {
12414             DisplayMoveError(_("It is Black's turn"));
12415             return;
12416         }
12417         moveType = WhiteDrop;
12418         break;
12419       case IcsPlayingBlack:
12420       case MachinePlaysWhite:
12421         if (WhiteOnMove(currentMove)) {
12422             DisplayMoveError(_("It is White's turn"));
12423             return;
12424         }
12425         moveType = BlackDrop;
12426         break;
12427       case EditGame:
12428         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12429         break;
12430       default:
12431         return;
12432     }
12433
12434     if (moveType == BlackDrop && selection < BlackPawn) {
12435       selection = (ChessSquare) ((int) selection
12436                                  + (int) BlackPawn - (int) WhitePawn);
12437     }
12438     if (boards[currentMove][y][x] != EmptySquare) {
12439         DisplayMoveError(_("That square is occupied"));
12440         return;
12441     }
12442
12443     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12444 }
12445
12446 void
12447 AcceptEvent()
12448 {
12449     /* Accept a pending offer of any kind from opponent */
12450
12451     if (appData.icsActive) {
12452         SendToICS(ics_prefix);
12453         SendToICS("accept\n");
12454     } else if (cmailMsgLoaded) {
12455         if (currentMove == cmailOldMove &&
12456             commentList[cmailOldMove] != NULL &&
12457             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12458                    "Black offers a draw" : "White offers a draw")) {
12459             TruncateGame();
12460             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12461             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12462         } else {
12463             DisplayError(_("There is no pending offer on this move"), 0);
12464             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12465         }
12466     } else {
12467         /* Not used for offers from chess program */
12468     }
12469 }
12470
12471 void
12472 DeclineEvent()
12473 {
12474     /* Decline a pending offer of any kind from opponent */
12475
12476     if (appData.icsActive) {
12477         SendToICS(ics_prefix);
12478         SendToICS("decline\n");
12479     } else if (cmailMsgLoaded) {
12480         if (currentMove == cmailOldMove &&
12481             commentList[cmailOldMove] != NULL &&
12482             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12483                    "Black offers a draw" : "White offers a draw")) {
12484 #ifdef NOTDEF
12485             AppendComment(cmailOldMove, "Draw declined", TRUE);
12486             DisplayComment(cmailOldMove - 1, "Draw declined");
12487 #endif /*NOTDEF*/
12488         } else {
12489             DisplayError(_("There is no pending offer on this move"), 0);
12490         }
12491     } else {
12492         /* Not used for offers from chess program */
12493     }
12494 }
12495
12496 void
12497 RematchEvent()
12498 {
12499     /* Issue ICS rematch command */
12500     if (appData.icsActive) {
12501         SendToICS(ics_prefix);
12502         SendToICS("rematch\n");
12503     }
12504 }
12505
12506 void
12507 CallFlagEvent()
12508 {
12509     /* Call your opponent's flag (claim a win on time) */
12510     if (appData.icsActive) {
12511         SendToICS(ics_prefix);
12512         SendToICS("flag\n");
12513     } else {
12514         switch (gameMode) {
12515           default:
12516             return;
12517           case MachinePlaysWhite:
12518             if (whiteFlag) {
12519                 if (blackFlag)
12520                   GameEnds(GameIsDrawn, "Both players ran out of time",
12521                            GE_PLAYER);
12522                 else
12523                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12524             } else {
12525                 DisplayError(_("Your opponent is not out of time"), 0);
12526             }
12527             break;
12528           case MachinePlaysBlack:
12529             if (blackFlag) {
12530                 if (whiteFlag)
12531                   GameEnds(GameIsDrawn, "Both players ran out of time",
12532                            GE_PLAYER);
12533                 else
12534                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12535             } else {
12536                 DisplayError(_("Your opponent is not out of time"), 0);
12537             }
12538             break;
12539         }
12540     }
12541 }
12542
12543 void
12544 DrawEvent()
12545 {
12546     /* Offer draw or accept pending draw offer from opponent */
12547
12548     if (appData.icsActive) {
12549         /* Note: tournament rules require draw offers to be
12550            made after you make your move but before you punch
12551            your clock.  Currently ICS doesn't let you do that;
12552            instead, you immediately punch your clock after making
12553            a move, but you can offer a draw at any time. */
12554
12555         SendToICS(ics_prefix);
12556         SendToICS("draw\n");
12557         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12558     } else if (cmailMsgLoaded) {
12559         if (currentMove == cmailOldMove &&
12560             commentList[cmailOldMove] != NULL &&
12561             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12562                    "Black offers a draw" : "White offers a draw")) {
12563             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12564             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12565         } else if (currentMove == cmailOldMove + 1) {
12566             char *offer = WhiteOnMove(cmailOldMove) ?
12567               "White offers a draw" : "Black offers a draw";
12568             AppendComment(currentMove, offer, TRUE);
12569             DisplayComment(currentMove - 1, offer);
12570             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12571         } else {
12572             DisplayError(_("You must make your move before offering a draw"), 0);
12573             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12574         }
12575     } else if (first.offeredDraw) {
12576         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12577     } else {
12578         if (first.sendDrawOffers) {
12579             SendToProgram("draw\n", &first);
12580             userOfferedDraw = TRUE;
12581         }
12582     }
12583 }
12584
12585 void
12586 AdjournEvent()
12587 {
12588     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12589
12590     if (appData.icsActive) {
12591         SendToICS(ics_prefix);
12592         SendToICS("adjourn\n");
12593     } else {
12594         /* Currently GNU Chess doesn't offer or accept Adjourns */
12595     }
12596 }
12597
12598
12599 void
12600 AbortEvent()
12601 {
12602     /* Offer Abort or accept pending Abort offer from opponent */
12603
12604     if (appData.icsActive) {
12605         SendToICS(ics_prefix);
12606         SendToICS("abort\n");
12607     } else {
12608         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12609     }
12610 }
12611
12612 void
12613 ResignEvent()
12614 {
12615     /* Resign.  You can do this even if it's not your turn. */
12616
12617     if (appData.icsActive) {
12618         SendToICS(ics_prefix);
12619         SendToICS("resign\n");
12620     } else {
12621         switch (gameMode) {
12622           case MachinePlaysWhite:
12623             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12624             break;
12625           case MachinePlaysBlack:
12626             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12627             break;
12628           case EditGame:
12629             if (cmailMsgLoaded) {
12630                 TruncateGame();
12631                 if (WhiteOnMove(cmailOldMove)) {
12632                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12633                 } else {
12634                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12635                 }
12636                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12637             }
12638             break;
12639           default:
12640             break;
12641         }
12642     }
12643 }
12644
12645
12646 void
12647 StopObservingEvent()
12648 {
12649     /* Stop observing current games */
12650     SendToICS(ics_prefix);
12651     SendToICS("unobserve\n");
12652 }
12653
12654 void
12655 StopExaminingEvent()
12656 {
12657     /* Stop observing current game */
12658     SendToICS(ics_prefix);
12659     SendToICS("unexamine\n");
12660 }
12661
12662 void
12663 ForwardInner(target)
12664      int target;
12665 {
12666     int limit;
12667
12668     if (appData.debugMode)
12669         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12670                 target, currentMove, forwardMostMove);
12671
12672     if (gameMode == EditPosition)
12673       return;
12674
12675     if (gameMode == PlayFromGameFile && !pausing)
12676       PauseEvent();
12677
12678     if (gameMode == IcsExamining && pausing)
12679       limit = pauseExamForwardMostMove;
12680     else
12681       limit = forwardMostMove;
12682
12683     if (target > limit) target = limit;
12684
12685     if (target > 0 && moveList[target - 1][0]) {
12686         int fromX, fromY, toX, toY;
12687         toX = moveList[target - 1][2] - AAA;
12688         toY = moveList[target - 1][3] - ONE;
12689         if (moveList[target - 1][1] == '@') {
12690             if (appData.highlightLastMove) {
12691                 SetHighlights(-1, -1, toX, toY);
12692             }
12693         } else {
12694             fromX = moveList[target - 1][0] - AAA;
12695             fromY = moveList[target - 1][1] - ONE;
12696             if (target == currentMove + 1) {
12697                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12698             }
12699             if (appData.highlightLastMove) {
12700                 SetHighlights(fromX, fromY, toX, toY);
12701             }
12702         }
12703     }
12704     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12705         gameMode == Training || gameMode == PlayFromGameFile ||
12706         gameMode == AnalyzeFile) {
12707         while (currentMove < target) {
12708             SendMoveToProgram(currentMove++, &first);
12709         }
12710     } else {
12711         currentMove = target;
12712     }
12713
12714     if (gameMode == EditGame || gameMode == EndOfGame) {
12715         whiteTimeRemaining = timeRemaining[0][currentMove];
12716         blackTimeRemaining = timeRemaining[1][currentMove];
12717     }
12718     DisplayBothClocks();
12719     DisplayMove(currentMove - 1);
12720     DrawPosition(FALSE, boards[currentMove]);
12721     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12722     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12723         DisplayComment(currentMove - 1, commentList[currentMove]);
12724     }
12725 }
12726
12727
12728 void
12729 ForwardEvent()
12730 {
12731     if (gameMode == IcsExamining && !pausing) {
12732         SendToICS(ics_prefix);
12733         SendToICS("forward\n");
12734     } else {
12735         ForwardInner(currentMove + 1);
12736     }
12737 }
12738
12739 void
12740 ToEndEvent()
12741 {
12742     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12743         /* to optimze, we temporarily turn off analysis mode while we feed
12744          * the remaining moves to the engine. Otherwise we get analysis output
12745          * after each move.
12746          */
12747         if (first.analysisSupport) {
12748           SendToProgram("exit\nforce\n", &first);
12749           first.analyzing = FALSE;
12750         }
12751     }
12752
12753     if (gameMode == IcsExamining && !pausing) {
12754         SendToICS(ics_prefix);
12755         SendToICS("forward 999999\n");
12756     } else {
12757         ForwardInner(forwardMostMove);
12758     }
12759
12760     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12761         /* we have fed all the moves, so reactivate analysis mode */
12762         SendToProgram("analyze\n", &first);
12763         first.analyzing = TRUE;
12764         /*first.maybeThinking = TRUE;*/
12765         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12766     }
12767 }
12768
12769 void
12770 BackwardInner(target)
12771      int target;
12772 {
12773     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12774
12775     if (appData.debugMode)
12776         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12777                 target, currentMove, forwardMostMove);
12778
12779     if (gameMode == EditPosition) return;
12780     if (currentMove <= backwardMostMove) {
12781         ClearHighlights();
12782         DrawPosition(full_redraw, boards[currentMove]);
12783         return;
12784     }
12785     if (gameMode == PlayFromGameFile && !pausing)
12786       PauseEvent();
12787
12788     if (moveList[target][0]) {
12789         int fromX, fromY, toX, toY;
12790         toX = moveList[target][2] - AAA;
12791         toY = moveList[target][3] - ONE;
12792         if (moveList[target][1] == '@') {
12793             if (appData.highlightLastMove) {
12794                 SetHighlights(-1, -1, toX, toY);
12795             }
12796         } else {
12797             fromX = moveList[target][0] - AAA;
12798             fromY = moveList[target][1] - ONE;
12799             if (target == currentMove - 1) {
12800                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12801             }
12802             if (appData.highlightLastMove) {
12803                 SetHighlights(fromX, fromY, toX, toY);
12804             }
12805         }
12806     }
12807     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12808         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12809         while (currentMove > target) {
12810             SendToProgram("undo\n", &first);
12811             currentMove--;
12812         }
12813     } else {
12814         currentMove = target;
12815     }
12816
12817     if (gameMode == EditGame || gameMode == EndOfGame) {
12818         whiteTimeRemaining = timeRemaining[0][currentMove];
12819         blackTimeRemaining = timeRemaining[1][currentMove];
12820     }
12821     DisplayBothClocks();
12822     DisplayMove(currentMove - 1);
12823     DrawPosition(full_redraw, boards[currentMove]);
12824     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12825     // [HGM] PV info: routine tests if comment empty
12826     DisplayComment(currentMove - 1, commentList[currentMove]);
12827 }
12828
12829 void
12830 BackwardEvent()
12831 {
12832     if (gameMode == IcsExamining && !pausing) {
12833         SendToICS(ics_prefix);
12834         SendToICS("backward\n");
12835     } else {
12836         BackwardInner(currentMove - 1);
12837     }
12838 }
12839
12840 void
12841 ToStartEvent()
12842 {
12843     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12844         /* to optimize, we temporarily turn off analysis mode while we undo
12845          * all the moves. Otherwise we get analysis output after each undo.
12846          */
12847         if (first.analysisSupport) {
12848           SendToProgram("exit\nforce\n", &first);
12849           first.analyzing = FALSE;
12850         }
12851     }
12852
12853     if (gameMode == IcsExamining && !pausing) {
12854         SendToICS(ics_prefix);
12855         SendToICS("backward 999999\n");
12856     } else {
12857         BackwardInner(backwardMostMove);
12858     }
12859
12860     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12861         /* we have fed all the moves, so reactivate analysis mode */
12862         SendToProgram("analyze\n", &first);
12863         first.analyzing = TRUE;
12864         /*first.maybeThinking = TRUE;*/
12865         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12866     }
12867 }
12868
12869 void
12870 ToNrEvent(int to)
12871 {
12872   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12873   if (to >= forwardMostMove) to = forwardMostMove;
12874   if (to <= backwardMostMove) to = backwardMostMove;
12875   if (to < currentMove) {
12876     BackwardInner(to);
12877   } else {
12878     ForwardInner(to);
12879   }
12880 }
12881
12882 void
12883 RevertEvent(Boolean annotate)
12884 {
12885     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12886         return;
12887     }
12888     if (gameMode != IcsExamining) {
12889         DisplayError(_("You are not examining a game"), 0);
12890         return;
12891     }
12892     if (pausing) {
12893         DisplayError(_("You can't revert while pausing"), 0);
12894         return;
12895     }
12896     SendToICS(ics_prefix);
12897     SendToICS("revert\n");
12898 }
12899
12900 void
12901 RetractMoveEvent()
12902 {
12903     switch (gameMode) {
12904       case MachinePlaysWhite:
12905       case MachinePlaysBlack:
12906         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12907             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12908             return;
12909         }
12910         if (forwardMostMove < 2) return;
12911         currentMove = forwardMostMove = forwardMostMove - 2;
12912         whiteTimeRemaining = timeRemaining[0][currentMove];
12913         blackTimeRemaining = timeRemaining[1][currentMove];
12914         DisplayBothClocks();
12915         DisplayMove(currentMove - 1);
12916         ClearHighlights();/*!! could figure this out*/
12917         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12918         SendToProgram("remove\n", &first);
12919         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12920         break;
12921
12922       case BeginningOfGame:
12923       default:
12924         break;
12925
12926       case IcsPlayingWhite:
12927       case IcsPlayingBlack:
12928         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12929             SendToICS(ics_prefix);
12930             SendToICS("takeback 2\n");
12931         } else {
12932             SendToICS(ics_prefix);
12933             SendToICS("takeback 1\n");
12934         }
12935         break;
12936     }
12937 }
12938
12939 void
12940 MoveNowEvent()
12941 {
12942     ChessProgramState *cps;
12943
12944     switch (gameMode) {
12945       case MachinePlaysWhite:
12946         if (!WhiteOnMove(forwardMostMove)) {
12947             DisplayError(_("It is your turn"), 0);
12948             return;
12949         }
12950         cps = &first;
12951         break;
12952       case MachinePlaysBlack:
12953         if (WhiteOnMove(forwardMostMove)) {
12954             DisplayError(_("It is your turn"), 0);
12955             return;
12956         }
12957         cps = &first;
12958         break;
12959       case TwoMachinesPlay:
12960         if (WhiteOnMove(forwardMostMove) ==
12961             (first.twoMachinesColor[0] == 'w')) {
12962             cps = &first;
12963         } else {
12964             cps = &second;
12965         }
12966         break;
12967       case BeginningOfGame:
12968       default:
12969         return;
12970     }
12971     SendToProgram("?\n", cps);
12972 }
12973
12974 void
12975 TruncateGameEvent()
12976 {
12977     EditGameEvent();
12978     if (gameMode != EditGame) return;
12979     TruncateGame();
12980 }
12981
12982 void
12983 TruncateGame()
12984 {
12985     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12986     if (forwardMostMove > currentMove) {
12987         if (gameInfo.resultDetails != NULL) {
12988             free(gameInfo.resultDetails);
12989             gameInfo.resultDetails = NULL;
12990             gameInfo.result = GameUnfinished;
12991         }
12992         forwardMostMove = currentMove;
12993         HistorySet(parseList, backwardMostMove, forwardMostMove,
12994                    currentMove-1);
12995     }
12996 }
12997
12998 void
12999 HintEvent()
13000 {
13001     if (appData.noChessProgram) return;
13002     switch (gameMode) {
13003       case MachinePlaysWhite:
13004         if (WhiteOnMove(forwardMostMove)) {
13005             DisplayError(_("Wait until your turn"), 0);
13006             return;
13007         }
13008         break;
13009       case BeginningOfGame:
13010       case MachinePlaysBlack:
13011         if (!WhiteOnMove(forwardMostMove)) {
13012             DisplayError(_("Wait until your turn"), 0);
13013             return;
13014         }
13015         break;
13016       default:
13017         DisplayError(_("No hint available"), 0);
13018         return;
13019     }
13020     SendToProgram("hint\n", &first);
13021     hintRequested = TRUE;
13022 }
13023
13024 void
13025 BookEvent()
13026 {
13027     if (appData.noChessProgram) return;
13028     switch (gameMode) {
13029       case MachinePlaysWhite:
13030         if (WhiteOnMove(forwardMostMove)) {
13031             DisplayError(_("Wait until your turn"), 0);
13032             return;
13033         }
13034         break;
13035       case BeginningOfGame:
13036       case MachinePlaysBlack:
13037         if (!WhiteOnMove(forwardMostMove)) {
13038             DisplayError(_("Wait until your turn"), 0);
13039             return;
13040         }
13041         break;
13042       case EditPosition:
13043         EditPositionDone(TRUE);
13044         break;
13045       case TwoMachinesPlay:
13046         return;
13047       default:
13048         break;
13049     }
13050     SendToProgram("bk\n", &first);
13051     bookOutput[0] = NULLCHAR;
13052     bookRequested = TRUE;
13053 }
13054
13055 void
13056 AboutGameEvent()
13057 {
13058     char *tags = PGNTags(&gameInfo);
13059     TagsPopUp(tags, CmailMsg());
13060     free(tags);
13061 }
13062
13063 /* end button procedures */
13064
13065 void
13066 PrintPosition(fp, move)
13067      FILE *fp;
13068      int move;
13069 {
13070     int i, j;
13071
13072     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13073         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13074             char c = PieceToChar(boards[move][i][j]);
13075             fputc(c == 'x' ? '.' : c, fp);
13076             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13077         }
13078     }
13079     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13080       fprintf(fp, "white to play\n");
13081     else
13082       fprintf(fp, "black to play\n");
13083 }
13084
13085 void
13086 PrintOpponents(fp)
13087      FILE *fp;
13088 {
13089     if (gameInfo.white != NULL) {
13090         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13091     } else {
13092         fprintf(fp, "\n");
13093     }
13094 }
13095
13096 /* Find last component of program's own name, using some heuristics */
13097 void
13098 TidyProgramName(prog, host, buf)
13099      char *prog, *host, buf[MSG_SIZ];
13100 {
13101     char *p, *q;
13102     int local = (strcmp(host, "localhost") == 0);
13103     while (!local && (p = strchr(prog, ';')) != NULL) {
13104         p++;
13105         while (*p == ' ') p++;
13106         prog = p;
13107     }
13108     if (*prog == '"' || *prog == '\'') {
13109         q = strchr(prog + 1, *prog);
13110     } else {
13111         q = strchr(prog, ' ');
13112     }
13113     if (q == NULL) q = prog + strlen(prog);
13114     p = q;
13115     while (p >= prog && *p != '/' && *p != '\\') p--;
13116     p++;
13117     if(p == prog && *p == '"') p++;
13118     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13119     memcpy(buf, p, q - p);
13120     buf[q - p] = NULLCHAR;
13121     if (!local) {
13122         strcat(buf, "@");
13123         strcat(buf, host);
13124     }
13125 }
13126
13127 char *
13128 TimeControlTagValue()
13129 {
13130     char buf[MSG_SIZ];
13131     if (!appData.clockMode) {
13132       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13133     } else if (movesPerSession > 0) {
13134       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13135     } else if (timeIncrement == 0) {
13136       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13137     } else {
13138       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13139     }
13140     return StrSave(buf);
13141 }
13142
13143 void
13144 SetGameInfo()
13145 {
13146     /* This routine is used only for certain modes */
13147     VariantClass v = gameInfo.variant;
13148     ChessMove r = GameUnfinished;
13149     char *p = NULL;
13150
13151     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13152         r = gameInfo.result;
13153         p = gameInfo.resultDetails;
13154         gameInfo.resultDetails = NULL;
13155     }
13156     ClearGameInfo(&gameInfo);
13157     gameInfo.variant = v;
13158
13159     switch (gameMode) {
13160       case MachinePlaysWhite:
13161         gameInfo.event = StrSave( appData.pgnEventHeader );
13162         gameInfo.site = StrSave(HostName());
13163         gameInfo.date = PGNDate();
13164         gameInfo.round = StrSave("-");
13165         gameInfo.white = StrSave(first.tidy);
13166         gameInfo.black = StrSave(UserName());
13167         gameInfo.timeControl = TimeControlTagValue();
13168         break;
13169
13170       case MachinePlaysBlack:
13171         gameInfo.event = StrSave( appData.pgnEventHeader );
13172         gameInfo.site = StrSave(HostName());
13173         gameInfo.date = PGNDate();
13174         gameInfo.round = StrSave("-");
13175         gameInfo.white = StrSave(UserName());
13176         gameInfo.black = StrSave(first.tidy);
13177         gameInfo.timeControl = TimeControlTagValue();
13178         break;
13179
13180       case TwoMachinesPlay:
13181         gameInfo.event = StrSave( appData.pgnEventHeader );
13182         gameInfo.site = StrSave(HostName());
13183         gameInfo.date = PGNDate();
13184         if (matchGame > 0) {
13185             char buf[MSG_SIZ];
13186             snprintf(buf, MSG_SIZ, "%d", matchGame);
13187             gameInfo.round = StrSave(buf);
13188         } else {
13189             gameInfo.round = StrSave("-");
13190         }
13191         if (first.twoMachinesColor[0] == 'w') {
13192             gameInfo.white = StrSave(first.tidy);
13193             gameInfo.black = StrSave(second.tidy);
13194         } else {
13195             gameInfo.white = StrSave(second.tidy);
13196             gameInfo.black = StrSave(first.tidy);
13197         }
13198         gameInfo.timeControl = TimeControlTagValue();
13199         break;
13200
13201       case EditGame:
13202         gameInfo.event = StrSave("Edited game");
13203         gameInfo.site = StrSave(HostName());
13204         gameInfo.date = PGNDate();
13205         gameInfo.round = StrSave("-");
13206         gameInfo.white = StrSave("-");
13207         gameInfo.black = StrSave("-");
13208         gameInfo.result = r;
13209         gameInfo.resultDetails = p;
13210         break;
13211
13212       case EditPosition:
13213         gameInfo.event = StrSave("Edited position");
13214         gameInfo.site = StrSave(HostName());
13215         gameInfo.date = PGNDate();
13216         gameInfo.round = StrSave("-");
13217         gameInfo.white = StrSave("-");
13218         gameInfo.black = StrSave("-");
13219         break;
13220
13221       case IcsPlayingWhite:
13222       case IcsPlayingBlack:
13223       case IcsObserving:
13224       case IcsExamining:
13225         break;
13226
13227       case PlayFromGameFile:
13228         gameInfo.event = StrSave("Game from non-PGN file");
13229         gameInfo.site = StrSave(HostName());
13230         gameInfo.date = PGNDate();
13231         gameInfo.round = StrSave("-");
13232         gameInfo.white = StrSave("?");
13233         gameInfo.black = StrSave("?");
13234         break;
13235
13236       default:
13237         break;
13238     }
13239 }
13240
13241 void
13242 ReplaceComment(index, text)
13243      int index;
13244      char *text;
13245 {
13246     int len;
13247
13248     while (*text == '\n') text++;
13249     len = strlen(text);
13250     while (len > 0 && text[len - 1] == '\n') len--;
13251
13252     if (commentList[index] != NULL)
13253       free(commentList[index]);
13254
13255     if (len == 0) {
13256         commentList[index] = NULL;
13257         return;
13258     }
13259   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13260       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13261       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13262     commentList[index] = (char *) malloc(len + 2);
13263     strncpy(commentList[index], text, len);
13264     commentList[index][len] = '\n';
13265     commentList[index][len + 1] = NULLCHAR;
13266   } else {
13267     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13268     char *p;
13269     commentList[index] = (char *) malloc(len + 7);
13270     safeStrCpy(commentList[index], "{\n", 3);
13271     safeStrCpy(commentList[index]+2, text, len+1);
13272     commentList[index][len+2] = NULLCHAR;
13273     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13274     strcat(commentList[index], "\n}\n");
13275   }
13276 }
13277
13278 void
13279 CrushCRs(text)
13280      char *text;
13281 {
13282   char *p = text;
13283   char *q = text;
13284   char ch;
13285
13286   do {
13287     ch = *p++;
13288     if (ch == '\r') continue;
13289     *q++ = ch;
13290   } while (ch != '\0');
13291 }
13292
13293 void
13294 AppendComment(index, text, addBraces)
13295      int index;
13296      char *text;
13297      Boolean addBraces; // [HGM] braces: tells if we should add {}
13298 {
13299     int oldlen, len;
13300     char *old;
13301
13302 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13303     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13304
13305     CrushCRs(text);
13306     while (*text == '\n') text++;
13307     len = strlen(text);
13308     while (len > 0 && text[len - 1] == '\n') len--;
13309
13310     if (len == 0) return;
13311
13312     if (commentList[index] != NULL) {
13313         old = commentList[index];
13314         oldlen = strlen(old);
13315         while(commentList[index][oldlen-1] ==  '\n')
13316           commentList[index][--oldlen] = NULLCHAR;
13317         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13318         safeStrCpy(commentList[index], old, oldlen);
13319         free(old);
13320         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13321         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13322           if(addBraces) addBraces = FALSE; else { text++; len--; }
13323           while (*text == '\n') { text++; len--; }
13324           commentList[index][--oldlen] = NULLCHAR;
13325       }
13326         if(addBraces) strcat(commentList[index], "\n{\n");
13327         else          strcat(commentList[index], "\n");
13328         strcat(commentList[index], text);
13329         if(addBraces) strcat(commentList[index], "\n}\n");
13330         else          strcat(commentList[index], "\n");
13331     } else {
13332         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13333         if(addBraces)
13334           safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
13335         else commentList[index][0] = NULLCHAR;
13336         strcat(commentList[index], text);
13337         strcat(commentList[index], "\n");
13338         if(addBraces) strcat(commentList[index], "}\n");
13339     }
13340 }
13341
13342 static char * FindStr( char * text, char * sub_text )
13343 {
13344     char * result = strstr( text, sub_text );
13345
13346     if( result != NULL ) {
13347         result += strlen( sub_text );
13348     }
13349
13350     return result;
13351 }
13352
13353 /* [AS] Try to extract PV info from PGN comment */
13354 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13355 char *GetInfoFromComment( int index, char * text )
13356 {
13357     char * sep = text;
13358
13359     if( text != NULL && index > 0 ) {
13360         int score = 0;
13361         int depth = 0;
13362         int time = -1, sec = 0, deci;
13363         char * s_eval = FindStr( text, "[%eval " );
13364         char * s_emt = FindStr( text, "[%emt " );
13365
13366         if( s_eval != NULL || s_emt != NULL ) {
13367             /* New style */
13368             char delim;
13369
13370             if( s_eval != NULL ) {
13371                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13372                     return text;
13373                 }
13374
13375                 if( delim != ']' ) {
13376                     return text;
13377                 }
13378             }
13379
13380             if( s_emt != NULL ) {
13381             }
13382                 return text;
13383         }
13384         else {
13385             /* We expect something like: [+|-]nnn.nn/dd */
13386             int score_lo = 0;
13387
13388             if(*text != '{') return text; // [HGM] braces: must be normal comment
13389
13390             sep = strchr( text, '/' );
13391             if( sep == NULL || sep < (text+4) ) {
13392                 return text;
13393             }
13394
13395             time = -1; sec = -1; deci = -1;
13396             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13397                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13398                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13399                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13400                 return text;
13401             }
13402
13403             if( score_lo < 0 || score_lo >= 100 ) {
13404                 return text;
13405             }
13406
13407             if(sec >= 0) time = 600*time + 10*sec; else
13408             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13409
13410             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13411
13412             /* [HGM] PV time: now locate end of PV info */
13413             while( *++sep >= '0' && *sep <= '9'); // strip depth
13414             if(time >= 0)
13415             while( *++sep >= '0' && *sep <= '9'); // strip time
13416             if(sec >= 0)
13417             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13418             if(deci >= 0)
13419             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13420             while(*sep == ' ') sep++;
13421         }
13422
13423         if( depth <= 0 ) {
13424             return text;
13425         }
13426
13427         if( time < 0 ) {
13428             time = -1;
13429         }
13430
13431         pvInfoList[index-1].depth = depth;
13432         pvInfoList[index-1].score = score;
13433         pvInfoList[index-1].time  = 10*time; // centi-sec
13434         if(*sep == '}') *sep = 0; else *--sep = '{';
13435     }
13436     return sep;
13437 }
13438
13439 void
13440 SendToProgram(message, cps)
13441      char *message;
13442      ChessProgramState *cps;
13443 {
13444     int count, outCount, error;
13445     char buf[MSG_SIZ];
13446
13447     if (cps->pr == NULL) return;
13448     Attention(cps);
13449
13450     if (appData.debugMode) {
13451         TimeMark now;
13452         GetTimeMark(&now);
13453         fprintf(debugFP, "%ld >%-6s: %s",
13454                 SubtractTimeMarks(&now, &programStartTime),
13455                 cps->which, message);
13456     }
13457
13458     count = strlen(message);
13459     outCount = OutputToProcess(cps->pr, message, count, &error);
13460     if (outCount < count && !exiting
13461                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13462       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13463         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13464             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13465                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13466                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13467             } else {
13468                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13469             }
13470             gameInfo.resultDetails = StrSave(buf);
13471         }
13472         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13473     }
13474 }
13475
13476 void
13477 ReceiveFromProgram(isr, closure, message, count, error)
13478      InputSourceRef isr;
13479      VOIDSTAR closure;
13480      char *message;
13481      int count;
13482      int error;
13483 {
13484     char *end_str;
13485     char buf[MSG_SIZ];
13486     ChessProgramState *cps = (ChessProgramState *)closure;
13487
13488     if (isr != cps->isr) return; /* Killed intentionally */
13489     if (count <= 0) {
13490         if (count == 0) {
13491             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13492                     cps->which, cps->program);
13493         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13494                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13495                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13496                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13497                 } else {
13498                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13499                 }
13500                 gameInfo.resultDetails = StrSave(buf);
13501             }
13502             RemoveInputSource(cps->isr);
13503             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13504         } else {
13505             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13506                     cps->which, cps->program);
13507             RemoveInputSource(cps->isr);
13508
13509             /* [AS] Program is misbehaving badly... kill it */
13510             if( count == -2 ) {
13511                 DestroyChildProcess( cps->pr, 9 );
13512                 cps->pr = NoProc;
13513             }
13514
13515             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13516         }
13517         return;
13518     }
13519
13520     if ((end_str = strchr(message, '\r')) != NULL)
13521       *end_str = NULLCHAR;
13522     if ((end_str = strchr(message, '\n')) != NULL)
13523       *end_str = NULLCHAR;
13524
13525     if (appData.debugMode) {
13526         TimeMark now; int print = 1;
13527         char *quote = ""; char c; int i;
13528
13529         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13530                 char start = message[0];
13531                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13532                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13533                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13534                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13535                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13536                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13537                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13538                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13539                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13540                     print = (appData.engineComments >= 2);
13541                 }
13542                 message[0] = start; // restore original message
13543         }
13544         if(print) {
13545                 GetTimeMark(&now);
13546                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13547                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13548                         quote,
13549                         message);
13550         }
13551     }
13552
13553     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13554     if (appData.icsEngineAnalyze) {
13555         if (strstr(message, "whisper") != NULL ||
13556              strstr(message, "kibitz") != NULL ||
13557             strstr(message, "tellics") != NULL) return;
13558     }
13559
13560     HandleMachineMove(message, cps);
13561 }
13562
13563
13564 void
13565 SendTimeControl(cps, mps, tc, inc, sd, st)
13566      ChessProgramState *cps;
13567      int mps, inc, sd, st;
13568      long tc;
13569 {
13570     char buf[MSG_SIZ];
13571     int seconds;
13572
13573     if( timeControl_2 > 0 ) {
13574         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13575             tc = timeControl_2;
13576         }
13577     }
13578     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13579     inc /= cps->timeOdds;
13580     st  /= cps->timeOdds;
13581
13582     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13583
13584     if (st > 0) {
13585       /* Set exact time per move, normally using st command */
13586       if (cps->stKludge) {
13587         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13588         seconds = st % 60;
13589         if (seconds == 0) {
13590           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13591         } else {
13592           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13593         }
13594       } else {
13595         snprintf(buf, MSG_SIZ, "st %d\n", st);
13596       }
13597     } else {
13598       /* Set conventional or incremental time control, using level command */
13599       if (seconds == 0) {
13600         /* Note old gnuchess bug -- minutes:seconds used to not work.
13601            Fixed in later versions, but still avoid :seconds
13602            when seconds is 0. */
13603         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13604       } else {
13605         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13606                  seconds, inc/1000.);
13607       }
13608     }
13609     SendToProgram(buf, cps);
13610
13611     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13612     /* Orthogonally, limit search to given depth */
13613     if (sd > 0) {
13614       if (cps->sdKludge) {
13615         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13616       } else {
13617         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13618       }
13619       SendToProgram(buf, cps);
13620     }
13621
13622     if(cps->nps > 0) { /* [HGM] nps */
13623         if(cps->supportsNPS == FALSE)
13624           cps->nps = -1; // don't use if engine explicitly says not supported!
13625         else {
13626           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13627           SendToProgram(buf, cps);
13628         }
13629     }
13630 }
13631
13632 ChessProgramState *WhitePlayer()
13633 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13634 {
13635     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13636        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13637         return &second;
13638     return &first;
13639 }
13640
13641 void
13642 SendTimeRemaining(cps, machineWhite)
13643      ChessProgramState *cps;
13644      int /*boolean*/ machineWhite;
13645 {
13646     char message[MSG_SIZ];
13647     long time, otime;
13648
13649     /* Note: this routine must be called when the clocks are stopped
13650        or when they have *just* been set or switched; otherwise
13651        it will be off by the time since the current tick started.
13652     */
13653     if (machineWhite) {
13654         time = whiteTimeRemaining / 10;
13655         otime = blackTimeRemaining / 10;
13656     } else {
13657         time = blackTimeRemaining / 10;
13658         otime = whiteTimeRemaining / 10;
13659     }
13660     /* [HGM] translate opponent's time by time-odds factor */
13661     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13662     if (appData.debugMode) {
13663         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13664     }
13665
13666     if (time <= 0) time = 1;
13667     if (otime <= 0) otime = 1;
13668
13669     snprintf(message, MSG_SIZ, "time %ld\n", time);
13670     SendToProgram(message, cps);
13671
13672     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13673     SendToProgram(message, cps);
13674 }
13675
13676 int
13677 BoolFeature(p, name, loc, cps)
13678      char **p;
13679      char *name;
13680      int *loc;
13681      ChessProgramState *cps;
13682 {
13683   char buf[MSG_SIZ];
13684   int len = strlen(name);
13685   int val;
13686
13687   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13688     (*p) += len + 1;
13689     sscanf(*p, "%d", &val);
13690     *loc = (val != 0);
13691     while (**p && **p != ' ')
13692       (*p)++;
13693     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13694     SendToProgram(buf, cps);
13695     return TRUE;
13696   }
13697   return FALSE;
13698 }
13699
13700 int
13701 IntFeature(p, name, loc, cps)
13702      char **p;
13703      char *name;
13704      int *loc;
13705      ChessProgramState *cps;
13706 {
13707   char buf[MSG_SIZ];
13708   int len = strlen(name);
13709   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13710     (*p) += len + 1;
13711     sscanf(*p, "%d", loc);
13712     while (**p && **p != ' ') (*p)++;
13713     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13714     SendToProgram(buf, cps);
13715     return TRUE;
13716   }
13717   return FALSE;
13718 }
13719
13720 int
13721 StringFeature(p, name, loc, cps)
13722      char **p;
13723      char *name;
13724      char loc[];
13725      ChessProgramState *cps;
13726 {
13727   char buf[MSG_SIZ];
13728   int len = strlen(name);
13729   if (strncmp((*p), name, len) == 0
13730       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13731     (*p) += len + 2;
13732     sscanf(*p, "%[^\"]", loc);
13733     while (**p && **p != '\"') (*p)++;
13734     if (**p == '\"') (*p)++;
13735     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13736     SendToProgram(buf, cps);
13737     return TRUE;
13738   }
13739   return FALSE;
13740 }
13741
13742 int
13743 ParseOption(Option *opt, ChessProgramState *cps)
13744 // [HGM] options: process the string that defines an engine option, and determine
13745 // name, type, default value, and allowed value range
13746 {
13747         char *p, *q, buf[MSG_SIZ];
13748         int n, min = (-1)<<31, max = 1<<31, def;
13749
13750         if(p = strstr(opt->name, " -spin ")) {
13751             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13752             if(max < min) max = min; // enforce consistency
13753             if(def < min) def = min;
13754             if(def > max) def = max;
13755             opt->value = def;
13756             opt->min = min;
13757             opt->max = max;
13758             opt->type = Spin;
13759         } else if((p = strstr(opt->name, " -slider "))) {
13760             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13761             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13762             if(max < min) max = min; // enforce consistency
13763             if(def < min) def = min;
13764             if(def > max) def = max;
13765             opt->value = def;
13766             opt->min = min;
13767             opt->max = max;
13768             opt->type = Spin; // Slider;
13769         } else if((p = strstr(opt->name, " -string "))) {
13770             opt->textValue = p+9;
13771             opt->type = TextBox;
13772         } else if((p = strstr(opt->name, " -file "))) {
13773             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13774             opt->textValue = p+7;
13775             opt->type = TextBox; // FileName;
13776         } else if((p = strstr(opt->name, " -path "))) {
13777             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13778             opt->textValue = p+7;
13779             opt->type = TextBox; // PathName;
13780         } else if(p = strstr(opt->name, " -check ")) {
13781             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13782             opt->value = (def != 0);
13783             opt->type = CheckBox;
13784         } else if(p = strstr(opt->name, " -combo ")) {
13785             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13786             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13787             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13788             opt->value = n = 0;
13789             while(q = StrStr(q, " /// ")) {
13790                 n++; *q = 0;    // count choices, and null-terminate each of them
13791                 q += 5;
13792                 if(*q == '*') { // remember default, which is marked with * prefix
13793                     q++;
13794                     opt->value = n;
13795                 }
13796                 cps->comboList[cps->comboCnt++] = q;
13797             }
13798             cps->comboList[cps->comboCnt++] = NULL;
13799             opt->max = n + 1;
13800             opt->type = ComboBox;
13801         } else if(p = strstr(opt->name, " -button")) {
13802             opt->type = Button;
13803         } else if(p = strstr(opt->name, " -save")) {
13804             opt->type = SaveButton;
13805         } else return FALSE;
13806         *p = 0; // terminate option name
13807         // now look if the command-line options define a setting for this engine option.
13808         if(cps->optionSettings && cps->optionSettings[0])
13809             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13810         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13811           snprintf(buf, MSG_SIZ, "option %s", p);
13812                 if(p = strstr(buf, ",")) *p = 0;
13813                 strcat(buf, "\n");
13814                 SendToProgram(buf, cps);
13815         }
13816         return TRUE;
13817 }
13818
13819 void
13820 FeatureDone(cps, val)
13821      ChessProgramState* cps;
13822      int val;
13823 {
13824   DelayedEventCallback cb = GetDelayedEvent();
13825   if ((cb == InitBackEnd3 && cps == &first) ||
13826       (cb == TwoMachinesEventIfReady && cps == &second)) {
13827     CancelDelayedEvent();
13828     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13829   }
13830   cps->initDone = val;
13831 }
13832
13833 /* Parse feature command from engine */
13834 void
13835 ParseFeatures(args, cps)
13836      char* args;
13837      ChessProgramState *cps;
13838 {
13839   char *p = args;
13840   char *q;
13841   int val;
13842   char buf[MSG_SIZ];
13843
13844   for (;;) {
13845     while (*p == ' ') p++;
13846     if (*p == NULLCHAR) return;
13847
13848     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13849     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13850     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13851     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13852     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13853     if (BoolFeature(&p, "reuse", &val, cps)) {
13854       /* Engine can disable reuse, but can't enable it if user said no */
13855       if (!val) cps->reuse = FALSE;
13856       continue;
13857     }
13858     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13859     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13860       if (gameMode == TwoMachinesPlay) {
13861         DisplayTwoMachinesTitle();
13862       } else {
13863         DisplayTitle("");
13864       }
13865       continue;
13866     }
13867     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13868     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13869     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13870     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13871     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13872     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13873     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13874     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13875     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13876     if (IntFeature(&p, "done", &val, cps)) {
13877       FeatureDone(cps, val);
13878       continue;
13879     }
13880     /* Added by Tord: */
13881     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13882     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13883     /* End of additions by Tord */
13884
13885     /* [HGM] added features: */
13886     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13887     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13888     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13889     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13890     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13891     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13892     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13893         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13894           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13895             SendToProgram(buf, cps);
13896             continue;
13897         }
13898         if(cps->nrOptions >= MAX_OPTIONS) {
13899             cps->nrOptions--;
13900             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13901             DisplayError(buf, 0);
13902         }
13903         continue;
13904     }
13905     /* End of additions by HGM */
13906
13907     /* unknown feature: complain and skip */
13908     q = p;
13909     while (*q && *q != '=') q++;
13910     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13911     SendToProgram(buf, cps);
13912     p = q;
13913     if (*p == '=') {
13914       p++;
13915       if (*p == '\"') {
13916         p++;
13917         while (*p && *p != '\"') p++;
13918         if (*p == '\"') p++;
13919       } else {
13920         while (*p && *p != ' ') p++;
13921       }
13922     }
13923   }
13924
13925 }
13926
13927 void
13928 PeriodicUpdatesEvent(newState)
13929      int newState;
13930 {
13931     if (newState == appData.periodicUpdates)
13932       return;
13933
13934     appData.periodicUpdates=newState;
13935
13936     /* Display type changes, so update it now */
13937 //    DisplayAnalysis();
13938
13939     /* Get the ball rolling again... */
13940     if (newState) {
13941         AnalysisPeriodicEvent(1);
13942         StartAnalysisClock();
13943     }
13944 }
13945
13946 void
13947 PonderNextMoveEvent(newState)
13948      int newState;
13949 {
13950     if (newState == appData.ponderNextMove) return;
13951     if (gameMode == EditPosition) EditPositionDone(TRUE);
13952     if (newState) {
13953         SendToProgram("hard\n", &first);
13954         if (gameMode == TwoMachinesPlay) {
13955             SendToProgram("hard\n", &second);
13956         }
13957     } else {
13958         SendToProgram("easy\n", &first);
13959         thinkOutput[0] = NULLCHAR;
13960         if (gameMode == TwoMachinesPlay) {
13961             SendToProgram("easy\n", &second);
13962         }
13963     }
13964     appData.ponderNextMove = newState;
13965 }
13966
13967 void
13968 NewSettingEvent(option, feature, command, value)
13969      char *command;
13970      int option, value, *feature;
13971 {
13972     char buf[MSG_SIZ];
13973
13974     if (gameMode == EditPosition) EditPositionDone(TRUE);
13975     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
13976     if(feature == NULL || *feature) SendToProgram(buf, &first);
13977     if (gameMode == TwoMachinesPlay) {
13978         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13979     }
13980 }
13981
13982 void
13983 ShowThinkingEvent()
13984 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13985 {
13986     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13987     int newState = appData.showThinking
13988         // [HGM] thinking: other features now need thinking output as well
13989         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13990
13991     if (oldState == newState) return;
13992     oldState = newState;
13993     if (gameMode == EditPosition) EditPositionDone(TRUE);
13994     if (oldState) {
13995         SendToProgram("post\n", &first);
13996         if (gameMode == TwoMachinesPlay) {
13997             SendToProgram("post\n", &second);
13998         }
13999     } else {
14000         SendToProgram("nopost\n", &first);
14001         thinkOutput[0] = NULLCHAR;
14002         if (gameMode == TwoMachinesPlay) {
14003             SendToProgram("nopost\n", &second);
14004         }
14005     }
14006 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14007 }
14008
14009 void
14010 AskQuestionEvent(title, question, replyPrefix, which)
14011      char *title; char *question; char *replyPrefix; char *which;
14012 {
14013   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14014   if (pr == NoProc) return;
14015   AskQuestion(title, question, replyPrefix, pr);
14016 }
14017
14018 void
14019 DisplayMove(moveNumber)
14020      int moveNumber;
14021 {
14022     char message[MSG_SIZ];
14023     char res[MSG_SIZ];
14024     char cpThinkOutput[MSG_SIZ];
14025
14026     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14027
14028     if (moveNumber == forwardMostMove - 1 ||
14029         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14030
14031         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14032
14033         if (strchr(cpThinkOutput, '\n')) {
14034             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14035         }
14036     } else {
14037         *cpThinkOutput = NULLCHAR;
14038     }
14039
14040     /* [AS] Hide thinking from human user */
14041     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14042         *cpThinkOutput = NULLCHAR;
14043         if( thinkOutput[0] != NULLCHAR ) {
14044             int i;
14045
14046             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14047                 cpThinkOutput[i] = '.';
14048             }
14049             cpThinkOutput[i] = NULLCHAR;
14050             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14051         }
14052     }
14053
14054     if (moveNumber == forwardMostMove - 1 &&
14055         gameInfo.resultDetails != NULL) {
14056         if (gameInfo.resultDetails[0] == NULLCHAR) {
14057           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14058         } else {
14059           snprintf(res, MSG_SIZ, " {%s} %s",
14060                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14061         }
14062     } else {
14063         res[0] = NULLCHAR;
14064     }
14065
14066     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14067         DisplayMessage(res, cpThinkOutput);
14068     } else {
14069       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14070                 WhiteOnMove(moveNumber) ? " " : ".. ",
14071                 parseList[moveNumber], res);
14072         DisplayMessage(message, cpThinkOutput);
14073     }
14074 }
14075
14076 void
14077 DisplayComment(moveNumber, text)
14078      int moveNumber;
14079      char *text;
14080 {
14081     char title[MSG_SIZ];
14082     char buf[8000]; // comment can be long!
14083     int score, depth;
14084
14085     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14086       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14087     } else {
14088       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14089               WhiteOnMove(moveNumber) ? " " : ".. ",
14090               parseList[moveNumber]);
14091     }
14092     // [HGM] PV info: display PV info together with (or as) comment
14093     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14094       if(text == NULL) text = "";
14095       score = pvInfoList[moveNumber].score;
14096       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14097               depth, (pvInfoList[moveNumber].time+50)/100, text);
14098       text = buf;
14099     }
14100     if (text != NULL && (appData.autoDisplayComment || commentUp))
14101         CommentPopUp(title, text);
14102 }
14103
14104 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14105  * might be busy thinking or pondering.  It can be omitted if your
14106  * gnuchess is configured to stop thinking immediately on any user
14107  * input.  However, that gnuchess feature depends on the FIONREAD
14108  * ioctl, which does not work properly on some flavors of Unix.
14109  */
14110 void
14111 Attention(cps)
14112      ChessProgramState *cps;
14113 {
14114 #if ATTENTION
14115     if (!cps->useSigint) return;
14116     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14117     switch (gameMode) {
14118       case MachinePlaysWhite:
14119       case MachinePlaysBlack:
14120       case TwoMachinesPlay:
14121       case IcsPlayingWhite:
14122       case IcsPlayingBlack:
14123       case AnalyzeMode:
14124       case AnalyzeFile:
14125         /* Skip if we know it isn't thinking */
14126         if (!cps->maybeThinking) return;
14127         if (appData.debugMode)
14128           fprintf(debugFP, "Interrupting %s\n", cps->which);
14129         InterruptChildProcess(cps->pr);
14130         cps->maybeThinking = FALSE;
14131         break;
14132       default:
14133         break;
14134     }
14135 #endif /*ATTENTION*/
14136 }
14137
14138 int
14139 CheckFlags()
14140 {
14141     if (whiteTimeRemaining <= 0) {
14142         if (!whiteFlag) {
14143             whiteFlag = TRUE;
14144             if (appData.icsActive) {
14145                 if (appData.autoCallFlag &&
14146                     gameMode == IcsPlayingBlack && !blackFlag) {
14147                   SendToICS(ics_prefix);
14148                   SendToICS("flag\n");
14149                 }
14150             } else {
14151                 if (blackFlag) {
14152                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14153                 } else {
14154                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14155                     if (appData.autoCallFlag) {
14156                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14157                         return TRUE;
14158                     }
14159                 }
14160             }
14161         }
14162     }
14163     if (blackTimeRemaining <= 0) {
14164         if (!blackFlag) {
14165             blackFlag = TRUE;
14166             if (appData.icsActive) {
14167                 if (appData.autoCallFlag &&
14168                     gameMode == IcsPlayingWhite && !whiteFlag) {
14169                   SendToICS(ics_prefix);
14170                   SendToICS("flag\n");
14171                 }
14172             } else {
14173                 if (whiteFlag) {
14174                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14175                 } else {
14176                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14177                     if (appData.autoCallFlag) {
14178                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14179                         return TRUE;
14180                     }
14181                 }
14182             }
14183         }
14184     }
14185     return FALSE;
14186 }
14187
14188 void
14189 CheckTimeControl()
14190 {
14191     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14192         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14193
14194     /*
14195      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14196      */
14197     if ( !WhiteOnMove(forwardMostMove) ) {
14198         /* White made time control */
14199         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14200         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14201         /* [HGM] time odds: correct new time quota for time odds! */
14202                                             / WhitePlayer()->timeOdds;
14203         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14204     } else {
14205         lastBlack -= blackTimeRemaining;
14206         /* Black made time control */
14207         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14208                                             / WhitePlayer()->other->timeOdds;
14209         lastWhite = whiteTimeRemaining;
14210     }
14211 }
14212
14213 void
14214 DisplayBothClocks()
14215 {
14216     int wom = gameMode == EditPosition ?
14217       !blackPlaysFirst : WhiteOnMove(currentMove);
14218     DisplayWhiteClock(whiteTimeRemaining, wom);
14219     DisplayBlackClock(blackTimeRemaining, !wom);
14220 }
14221
14222
14223 /* Timekeeping seems to be a portability nightmare.  I think everyone
14224    has ftime(), but I'm really not sure, so I'm including some ifdefs
14225    to use other calls if you don't.  Clocks will be less accurate if
14226    you have neither ftime nor gettimeofday.
14227 */
14228
14229 /* VS 2008 requires the #include outside of the function */
14230 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14231 #include <sys/timeb.h>
14232 #endif
14233
14234 /* Get the current time as a TimeMark */
14235 void
14236 GetTimeMark(tm)
14237      TimeMark *tm;
14238 {
14239 #if HAVE_GETTIMEOFDAY
14240
14241     struct timeval timeVal;
14242     struct timezone timeZone;
14243
14244     gettimeofday(&timeVal, &timeZone);
14245     tm->sec = (long) timeVal.tv_sec;
14246     tm->ms = (int) (timeVal.tv_usec / 1000L);
14247
14248 #else /*!HAVE_GETTIMEOFDAY*/
14249 #if HAVE_FTIME
14250
14251 // include <sys/timeb.h> / moved to just above start of function
14252     struct timeb timeB;
14253
14254     ftime(&timeB);
14255     tm->sec = (long) timeB.time;
14256     tm->ms = (int) timeB.millitm;
14257
14258 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14259     tm->sec = (long) time(NULL);
14260     tm->ms = 0;
14261 #endif
14262 #endif
14263 }
14264
14265 /* Return the difference in milliseconds between two
14266    time marks.  We assume the difference will fit in a long!
14267 */
14268 long
14269 SubtractTimeMarks(tm2, tm1)
14270      TimeMark *tm2, *tm1;
14271 {
14272     return 1000L*(tm2->sec - tm1->sec) +
14273            (long) (tm2->ms - tm1->ms);
14274 }
14275
14276
14277 /*
14278  * Code to manage the game clocks.
14279  *
14280  * In tournament play, black starts the clock and then white makes a move.
14281  * We give the human user a slight advantage if he is playing white---the
14282  * clocks don't run until he makes his first move, so it takes zero time.
14283  * Also, we don't account for network lag, so we could get out of sync
14284  * with GNU Chess's clock -- but then, referees are always right.
14285  */
14286
14287 static TimeMark tickStartTM;
14288 static long intendedTickLength;
14289
14290 long
14291 NextTickLength(timeRemaining)
14292      long timeRemaining;
14293 {
14294     long nominalTickLength, nextTickLength;
14295
14296     if (timeRemaining > 0L && timeRemaining <= 10000L)
14297       nominalTickLength = 100L;
14298     else
14299       nominalTickLength = 1000L;
14300     nextTickLength = timeRemaining % nominalTickLength;
14301     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14302
14303     return nextTickLength;
14304 }
14305
14306 /* Adjust clock one minute up or down */
14307 void
14308 AdjustClock(Boolean which, int dir)
14309 {
14310     if(which) blackTimeRemaining += 60000*dir;
14311     else      whiteTimeRemaining += 60000*dir;
14312     DisplayBothClocks();
14313 }
14314
14315 /* Stop clocks and reset to a fresh time control */
14316 void
14317 ResetClocks()
14318 {
14319     (void) StopClockTimer();
14320     if (appData.icsActive) {
14321         whiteTimeRemaining = blackTimeRemaining = 0;
14322     } else if (searchTime) {
14323         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14324         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14325     } else { /* [HGM] correct new time quote for time odds */
14326         whiteTC = blackTC = fullTimeControlString;
14327         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14328         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14329     }
14330     if (whiteFlag || blackFlag) {
14331         DisplayTitle("");
14332         whiteFlag = blackFlag = FALSE;
14333     }
14334     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14335     DisplayBothClocks();
14336 }
14337
14338 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14339
14340 /* Decrement running clock by amount of time that has passed */
14341 void
14342 DecrementClocks()
14343 {
14344     long timeRemaining;
14345     long lastTickLength, fudge;
14346     TimeMark now;
14347
14348     if (!appData.clockMode) return;
14349     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14350
14351     GetTimeMark(&now);
14352
14353     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14354
14355     /* Fudge if we woke up a little too soon */
14356     fudge = intendedTickLength - lastTickLength;
14357     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14358
14359     if (WhiteOnMove(forwardMostMove)) {
14360         if(whiteNPS >= 0) lastTickLength = 0;
14361         timeRemaining = whiteTimeRemaining -= lastTickLength;
14362         if(timeRemaining < 0 && !appData.icsActive) {
14363             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14364             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14365                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14366                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14367             }
14368         }
14369         DisplayWhiteClock(whiteTimeRemaining - fudge,
14370                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14371     } else {
14372         if(blackNPS >= 0) lastTickLength = 0;
14373         timeRemaining = blackTimeRemaining -= lastTickLength;
14374         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14375             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14376             if(suddenDeath) {
14377                 blackStartMove = forwardMostMove;
14378                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14379             }
14380         }
14381         DisplayBlackClock(blackTimeRemaining - fudge,
14382                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14383     }
14384     if (CheckFlags()) return;
14385
14386     tickStartTM = now;
14387     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14388     StartClockTimer(intendedTickLength);
14389
14390     /* if the time remaining has fallen below the alarm threshold, sound the
14391      * alarm. if the alarm has sounded and (due to a takeback or time control
14392      * with increment) the time remaining has increased to a level above the
14393      * threshold, reset the alarm so it can sound again.
14394      */
14395
14396     if (appData.icsActive && appData.icsAlarm) {
14397
14398         /* make sure we are dealing with the user's clock */
14399         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14400                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14401            )) return;
14402
14403         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14404             alarmSounded = FALSE;
14405         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14406             PlayAlarmSound();
14407             alarmSounded = TRUE;
14408         }
14409     }
14410 }
14411
14412
14413 /* A player has just moved, so stop the previously running
14414    clock and (if in clock mode) start the other one.
14415    We redisplay both clocks in case we're in ICS mode, because
14416    ICS gives us an update to both clocks after every move.
14417    Note that this routine is called *after* forwardMostMove
14418    is updated, so the last fractional tick must be subtracted
14419    from the color that is *not* on move now.
14420 */
14421 void
14422 SwitchClocks(int newMoveNr)
14423 {
14424     long lastTickLength;
14425     TimeMark now;
14426     int flagged = FALSE;
14427
14428     GetTimeMark(&now);
14429
14430     if (StopClockTimer() && appData.clockMode) {
14431         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14432         if (!WhiteOnMove(forwardMostMove)) {
14433             if(blackNPS >= 0) lastTickLength = 0;
14434             blackTimeRemaining -= lastTickLength;
14435            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14436 //         if(pvInfoList[forwardMostMove-1].time == -1)
14437                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14438                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14439         } else {
14440            if(whiteNPS >= 0) lastTickLength = 0;
14441            whiteTimeRemaining -= lastTickLength;
14442            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14443 //         if(pvInfoList[forwardMostMove-1].time == -1)
14444                  pvInfoList[forwardMostMove-1].time =
14445                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14446         }
14447         flagged = CheckFlags();
14448     }
14449     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14450     CheckTimeControl();
14451
14452     if (flagged || !appData.clockMode) return;
14453
14454     switch (gameMode) {
14455       case MachinePlaysBlack:
14456       case MachinePlaysWhite:
14457       case BeginningOfGame:
14458         if (pausing) return;
14459         break;
14460
14461       case EditGame:
14462       case PlayFromGameFile:
14463       case IcsExamining:
14464         return;
14465
14466       default:
14467         break;
14468     }
14469
14470     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14471         if(WhiteOnMove(forwardMostMove))
14472              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14473         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14474     }
14475
14476     tickStartTM = now;
14477     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14478       whiteTimeRemaining : blackTimeRemaining);
14479     StartClockTimer(intendedTickLength);
14480 }
14481
14482
14483 /* Stop both clocks */
14484 void
14485 StopClocks()
14486 {
14487     long lastTickLength;
14488     TimeMark now;
14489
14490     if (!StopClockTimer()) return;
14491     if (!appData.clockMode) return;
14492
14493     GetTimeMark(&now);
14494
14495     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14496     if (WhiteOnMove(forwardMostMove)) {
14497         if(whiteNPS >= 0) lastTickLength = 0;
14498         whiteTimeRemaining -= lastTickLength;
14499         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14500     } else {
14501         if(blackNPS >= 0) lastTickLength = 0;
14502         blackTimeRemaining -= lastTickLength;
14503         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14504     }
14505     CheckFlags();
14506 }
14507
14508 /* Start clock of player on move.  Time may have been reset, so
14509    if clock is already running, stop and restart it. */
14510 void
14511 StartClocks()
14512 {
14513     (void) StopClockTimer(); /* in case it was running already */
14514     DisplayBothClocks();
14515     if (CheckFlags()) return;
14516
14517     if (!appData.clockMode) return;
14518     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14519
14520     GetTimeMark(&tickStartTM);
14521     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14522       whiteTimeRemaining : blackTimeRemaining);
14523
14524    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14525     whiteNPS = blackNPS = -1;
14526     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14527        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14528         whiteNPS = first.nps;
14529     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14530        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14531         blackNPS = first.nps;
14532     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14533         whiteNPS = second.nps;
14534     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14535         blackNPS = second.nps;
14536     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14537
14538     StartClockTimer(intendedTickLength);
14539 }
14540
14541 char *
14542 TimeString(ms)
14543      long ms;
14544 {
14545     long second, minute, hour, day;
14546     char *sign = "";
14547     static char buf[32];
14548
14549     if (ms > 0 && ms <= 9900) {
14550       /* convert milliseconds to tenths, rounding up */
14551       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14552
14553       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14554       return buf;
14555     }
14556
14557     /* convert milliseconds to seconds, rounding up */
14558     /* use floating point to avoid strangeness of integer division
14559        with negative dividends on many machines */
14560     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14561
14562     if (second < 0) {
14563         sign = "-";
14564         second = -second;
14565     }
14566
14567     day = second / (60 * 60 * 24);
14568     second = second % (60 * 60 * 24);
14569     hour = second / (60 * 60);
14570     second = second % (60 * 60);
14571     minute = second / 60;
14572     second = second % 60;
14573
14574     if (day > 0)
14575       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14576               sign, day, hour, minute, second);
14577     else if (hour > 0)
14578       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14579     else
14580       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14581
14582     return buf;
14583 }
14584
14585
14586 /*
14587  * This is necessary because some C libraries aren't ANSI C compliant yet.
14588  */
14589 char *
14590 StrStr(string, match)
14591      char *string, *match;
14592 {
14593     int i, length;
14594
14595     length = strlen(match);
14596
14597     for (i = strlen(string) - length; i >= 0; i--, string++)
14598       if (!strncmp(match, string, length))
14599         return string;
14600
14601     return NULL;
14602 }
14603
14604 char *
14605 StrCaseStr(string, match)
14606      char *string, *match;
14607 {
14608     int i, j, length;
14609
14610     length = strlen(match);
14611
14612     for (i = strlen(string) - length; i >= 0; i--, string++) {
14613         for (j = 0; j < length; j++) {
14614             if (ToLower(match[j]) != ToLower(string[j]))
14615               break;
14616         }
14617         if (j == length) return string;
14618     }
14619
14620     return NULL;
14621 }
14622
14623 #ifndef _amigados
14624 int
14625 StrCaseCmp(s1, s2)
14626      char *s1, *s2;
14627 {
14628     char c1, c2;
14629
14630     for (;;) {
14631         c1 = ToLower(*s1++);
14632         c2 = ToLower(*s2++);
14633         if (c1 > c2) return 1;
14634         if (c1 < c2) return -1;
14635         if (c1 == NULLCHAR) return 0;
14636     }
14637 }
14638
14639
14640 int
14641 ToLower(c)
14642      int c;
14643 {
14644     return isupper(c) ? tolower(c) : c;
14645 }
14646
14647
14648 int
14649 ToUpper(c)
14650      int c;
14651 {
14652     return islower(c) ? toupper(c) : c;
14653 }
14654 #endif /* !_amigados    */
14655
14656 char *
14657 StrSave(s)
14658      char *s;
14659 {
14660   char *ret;
14661
14662   if ((ret = (char *) malloc(strlen(s) + 1)))
14663     {
14664       safeStrCpy(ret, s, strlen(s)+1);
14665     }
14666   return ret;
14667 }
14668
14669 char *
14670 StrSavePtr(s, savePtr)
14671      char *s, **savePtr;
14672 {
14673     if (*savePtr) {
14674         free(*savePtr);
14675     }
14676     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14677       safeStrCpy(*savePtr, s, strlen(s)+1);
14678     }
14679     return(*savePtr);
14680 }
14681
14682 char *
14683 PGNDate()
14684 {
14685     time_t clock;
14686     struct tm *tm;
14687     char buf[MSG_SIZ];
14688
14689     clock = time((time_t *)NULL);
14690     tm = localtime(&clock);
14691     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14692             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14693     return StrSave(buf);
14694 }
14695
14696
14697 char *
14698 PositionToFEN(move, overrideCastling)
14699      int move;
14700      char *overrideCastling;
14701 {
14702     int i, j, fromX, fromY, toX, toY;
14703     int whiteToPlay;
14704     char buf[128];
14705     char *p, *q;
14706     int emptycount;
14707     ChessSquare piece;
14708
14709     whiteToPlay = (gameMode == EditPosition) ?
14710       !blackPlaysFirst : (move % 2 == 0);
14711     p = buf;
14712
14713     /* Piece placement data */
14714     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14715         emptycount = 0;
14716         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14717             if (boards[move][i][j] == EmptySquare) {
14718                 emptycount++;
14719             } else { ChessSquare piece = boards[move][i][j];
14720                 if (emptycount > 0) {
14721                     if(emptycount<10) /* [HGM] can be >= 10 */
14722                         *p++ = '0' + emptycount;
14723                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14724                     emptycount = 0;
14725                 }
14726                 if(PieceToChar(piece) == '+') {
14727                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14728                     *p++ = '+';
14729                     piece = (ChessSquare)(DEMOTED piece);
14730                 }
14731                 *p++ = PieceToChar(piece);
14732                 if(p[-1] == '~') {
14733                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14734                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14735                     *p++ = '~';
14736                 }
14737             }
14738         }
14739         if (emptycount > 0) {
14740             if(emptycount<10) /* [HGM] can be >= 10 */
14741                 *p++ = '0' + emptycount;
14742             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14743             emptycount = 0;
14744         }
14745         *p++ = '/';
14746     }
14747     *(p - 1) = ' ';
14748
14749     /* [HGM] print Crazyhouse or Shogi holdings */
14750     if( gameInfo.holdingsWidth ) {
14751         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14752         q = p;
14753         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14754             piece = boards[move][i][BOARD_WIDTH-1];
14755             if( piece != EmptySquare )
14756               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14757                   *p++ = PieceToChar(piece);
14758         }
14759         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14760             piece = boards[move][BOARD_HEIGHT-i-1][0];
14761             if( piece != EmptySquare )
14762               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14763                   *p++ = PieceToChar(piece);
14764         }
14765
14766         if( q == p ) *p++ = '-';
14767         *p++ = ']';
14768         *p++ = ' ';
14769     }
14770
14771     /* Active color */
14772     *p++ = whiteToPlay ? 'w' : 'b';
14773     *p++ = ' ';
14774
14775   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14776     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14777   } else {
14778   if(nrCastlingRights) {
14779      q = p;
14780      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14781        /* [HGM] write directly from rights */
14782            if(boards[move][CASTLING][2] != NoRights &&
14783               boards[move][CASTLING][0] != NoRights   )
14784                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14785            if(boards[move][CASTLING][2] != NoRights &&
14786               boards[move][CASTLING][1] != NoRights   )
14787                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14788            if(boards[move][CASTLING][5] != NoRights &&
14789               boards[move][CASTLING][3] != NoRights   )
14790                 *p++ = boards[move][CASTLING][3] + AAA;
14791            if(boards[move][CASTLING][5] != NoRights &&
14792               boards[move][CASTLING][4] != NoRights   )
14793                 *p++ = boards[move][CASTLING][4] + AAA;
14794      } else {
14795
14796         /* [HGM] write true castling rights */
14797         if( nrCastlingRights == 6 ) {
14798             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14799                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14800             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14801                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14802             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14803                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14804             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14805                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14806         }
14807      }
14808      if (q == p) *p++ = '-'; /* No castling rights */
14809      *p++ = ' ';
14810   }
14811
14812   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14813      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14814     /* En passant target square */
14815     if (move > backwardMostMove) {
14816         fromX = moveList[move - 1][0] - AAA;
14817         fromY = moveList[move - 1][1] - ONE;
14818         toX = moveList[move - 1][2] - AAA;
14819         toY = moveList[move - 1][3] - ONE;
14820         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14821             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14822             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14823             fromX == toX) {
14824             /* 2-square pawn move just happened */
14825             *p++ = toX + AAA;
14826             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14827         } else {
14828             *p++ = '-';
14829         }
14830     } else if(move == backwardMostMove) {
14831         // [HGM] perhaps we should always do it like this, and forget the above?
14832         if((signed char)boards[move][EP_STATUS] >= 0) {
14833             *p++ = boards[move][EP_STATUS] + AAA;
14834             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14835         } else {
14836             *p++ = '-';
14837         }
14838     } else {
14839         *p++ = '-';
14840     }
14841     *p++ = ' ';
14842   }
14843   }
14844
14845     /* [HGM] find reversible plies */
14846     {   int i = 0, j=move;
14847
14848         if (appData.debugMode) { int k;
14849             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14850             for(k=backwardMostMove; k<=forwardMostMove; k++)
14851                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14852
14853         }
14854
14855         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14856         if( j == backwardMostMove ) i += initialRulePlies;
14857         sprintf(p, "%d ", i);
14858         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14859     }
14860     /* Fullmove number */
14861     sprintf(p, "%d", (move / 2) + 1);
14862
14863     return StrSave(buf);
14864 }
14865
14866 Boolean
14867 ParseFEN(board, blackPlaysFirst, fen)
14868     Board board;
14869      int *blackPlaysFirst;
14870      char *fen;
14871 {
14872     int i, j;
14873     char *p, c;
14874     int emptycount;
14875     ChessSquare piece;
14876
14877     p = fen;
14878
14879     /* [HGM] by default clear Crazyhouse holdings, if present */
14880     if(gameInfo.holdingsWidth) {
14881        for(i=0; i<BOARD_HEIGHT; i++) {
14882            board[i][0]             = EmptySquare; /* black holdings */
14883            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14884            board[i][1]             = (ChessSquare) 0; /* black counts */
14885            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14886        }
14887     }
14888
14889     /* Piece placement data */
14890     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14891         j = 0;
14892         for (;;) {
14893             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14894                 if (*p == '/') p++;
14895                 emptycount = gameInfo.boardWidth - j;
14896                 while (emptycount--)
14897                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14898                 break;
14899 #if(BOARD_FILES >= 10)
14900             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14901                 p++; emptycount=10;
14902                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14903                 while (emptycount--)
14904                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14905 #endif
14906             } else if (isdigit(*p)) {
14907                 emptycount = *p++ - '0';
14908                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14909                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14910                 while (emptycount--)
14911                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14912             } else if (*p == '+' || isalpha(*p)) {
14913                 if (j >= gameInfo.boardWidth) return FALSE;
14914                 if(*p=='+') {
14915                     piece = CharToPiece(*++p);
14916                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14917                     piece = (ChessSquare) (PROMOTED piece ); p++;
14918                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14919                 } else piece = CharToPiece(*p++);
14920
14921                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14922                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14923                     piece = (ChessSquare) (PROMOTED piece);
14924                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14925                     p++;
14926                 }
14927                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14928             } else {
14929                 return FALSE;
14930             }
14931         }
14932     }
14933     while (*p == '/' || *p == ' ') p++;
14934
14935     /* [HGM] look for Crazyhouse holdings here */
14936     while(*p==' ') p++;
14937     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14938         if(*p == '[') p++;
14939         if(*p == '-' ) p++; /* empty holdings */ else {
14940             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14941             /* if we would allow FEN reading to set board size, we would   */
14942             /* have to add holdings and shift the board read so far here   */
14943             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14944                 p++;
14945                 if((int) piece >= (int) BlackPawn ) {
14946                     i = (int)piece - (int)BlackPawn;
14947                     i = PieceToNumber((ChessSquare)i);
14948                     if( i >= gameInfo.holdingsSize ) return FALSE;
14949                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14950                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14951                 } else {
14952                     i = (int)piece - (int)WhitePawn;
14953                     i = PieceToNumber((ChessSquare)i);
14954                     if( i >= gameInfo.holdingsSize ) return FALSE;
14955                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14956                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14957                 }
14958             }
14959         }
14960         if(*p == ']') p++;
14961     }
14962
14963     while(*p == ' ') p++;
14964
14965     /* Active color */
14966     c = *p++;
14967     if(appData.colorNickNames) {
14968       if( c == appData.colorNickNames[0] ) c = 'w'; else
14969       if( c == appData.colorNickNames[1] ) c = 'b';
14970     }
14971     switch (c) {
14972       case 'w':
14973         *blackPlaysFirst = FALSE;
14974         break;
14975       case 'b':
14976         *blackPlaysFirst = TRUE;
14977         break;
14978       default:
14979         return FALSE;
14980     }
14981
14982     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14983     /* return the extra info in global variiables             */
14984
14985     /* set defaults in case FEN is incomplete */
14986     board[EP_STATUS] = EP_UNKNOWN;
14987     for(i=0; i<nrCastlingRights; i++ ) {
14988         board[CASTLING][i] =
14989             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14990     }   /* assume possible unless obviously impossible */
14991     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14992     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14993     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14994                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14995     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14996     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14997     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14998                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14999     FENrulePlies = 0;
15000
15001     while(*p==' ') p++;
15002     if(nrCastlingRights) {
15003       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15004           /* castling indicator present, so default becomes no castlings */
15005           for(i=0; i<nrCastlingRights; i++ ) {
15006                  board[CASTLING][i] = NoRights;
15007           }
15008       }
15009       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15010              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15011              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15012              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15013         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15014
15015         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15016             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15017             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15018         }
15019         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15020             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15021         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15022                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15023         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15024                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15025         switch(c) {
15026           case'K':
15027               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15028               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15029               board[CASTLING][2] = whiteKingFile;
15030               break;
15031           case'Q':
15032               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15033               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15034               board[CASTLING][2] = whiteKingFile;
15035               break;
15036           case'k':
15037               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15038               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15039               board[CASTLING][5] = blackKingFile;
15040               break;
15041           case'q':
15042               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15043               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15044               board[CASTLING][5] = blackKingFile;
15045           case '-':
15046               break;
15047           default: /* FRC castlings */
15048               if(c >= 'a') { /* black rights */
15049                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15050                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15051                   if(i == BOARD_RGHT) break;
15052                   board[CASTLING][5] = i;
15053                   c -= AAA;
15054                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15055                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15056                   if(c > i)
15057                       board[CASTLING][3] = c;
15058                   else
15059                       board[CASTLING][4] = c;
15060               } else { /* white rights */
15061                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15062                     if(board[0][i] == WhiteKing) break;
15063                   if(i == BOARD_RGHT) break;
15064                   board[CASTLING][2] = i;
15065                   c -= AAA - 'a' + 'A';
15066                   if(board[0][c] >= WhiteKing) break;
15067                   if(c > i)
15068                       board[CASTLING][0] = c;
15069                   else
15070                       board[CASTLING][1] = c;
15071               }
15072         }
15073       }
15074       for(i=0; i<nrCastlingRights; i++)
15075         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15076     if (appData.debugMode) {
15077         fprintf(debugFP, "FEN castling rights:");
15078         for(i=0; i<nrCastlingRights; i++)
15079         fprintf(debugFP, " %d", board[CASTLING][i]);
15080         fprintf(debugFP, "\n");
15081     }
15082
15083       while(*p==' ') p++;
15084     }
15085
15086     /* read e.p. field in games that know e.p. capture */
15087     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15088        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15089       if(*p=='-') {
15090         p++; board[EP_STATUS] = EP_NONE;
15091       } else {
15092          char c = *p++ - AAA;
15093
15094          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15095          if(*p >= '0' && *p <='9') p++;
15096          board[EP_STATUS] = c;
15097       }
15098     }
15099
15100
15101     if(sscanf(p, "%d", &i) == 1) {
15102         FENrulePlies = i; /* 50-move ply counter */
15103         /* (The move number is still ignored)    */
15104     }
15105
15106     return TRUE;
15107 }
15108
15109 void
15110 EditPositionPasteFEN(char *fen)
15111 {
15112   if (fen != NULL) {
15113     Board initial_position;
15114
15115     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15116       DisplayError(_("Bad FEN position in clipboard"), 0);
15117       return ;
15118     } else {
15119       int savedBlackPlaysFirst = blackPlaysFirst;
15120       EditPositionEvent();
15121       blackPlaysFirst = savedBlackPlaysFirst;
15122       CopyBoard(boards[0], initial_position);
15123       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15124       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15125       DisplayBothClocks();
15126       DrawPosition(FALSE, boards[currentMove]);
15127     }
15128   }
15129 }
15130
15131 static char cseq[12] = "\\   ";
15132
15133 Boolean set_cont_sequence(char *new_seq)
15134 {
15135     int len;
15136     Boolean ret;
15137
15138     // handle bad attempts to set the sequence
15139         if (!new_seq)
15140                 return 0; // acceptable error - no debug
15141
15142     len = strlen(new_seq);
15143     ret = (len > 0) && (len < sizeof(cseq));
15144     if (ret)
15145       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15146     else if (appData.debugMode)
15147       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15148     return ret;
15149 }
15150
15151 /*
15152     reformat a source message so words don't cross the width boundary.  internal
15153     newlines are not removed.  returns the wrapped size (no null character unless
15154     included in source message).  If dest is NULL, only calculate the size required
15155     for the dest buffer.  lp argument indicats line position upon entry, and it's
15156     passed back upon exit.
15157 */
15158 int wrap(char *dest, char *src, int count, int width, int *lp)
15159 {
15160     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15161
15162     cseq_len = strlen(cseq);
15163     old_line = line = *lp;
15164     ansi = len = clen = 0;
15165
15166     for (i=0; i < count; i++)
15167     {
15168         if (src[i] == '\033')
15169             ansi = 1;
15170
15171         // if we hit the width, back up
15172         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15173         {
15174             // store i & len in case the word is too long
15175             old_i = i, old_len = len;
15176
15177             // find the end of the last word
15178             while (i && src[i] != ' ' && src[i] != '\n')
15179             {
15180                 i--;
15181                 len--;
15182             }
15183
15184             // word too long?  restore i & len before splitting it
15185             if ((old_i-i+clen) >= width)
15186             {
15187                 i = old_i;
15188                 len = old_len;
15189             }
15190
15191             // extra space?
15192             if (i && src[i-1] == ' ')
15193                 len--;
15194
15195             if (src[i] != ' ' && src[i] != '\n')
15196             {
15197                 i--;
15198                 if (len)
15199                     len--;
15200             }
15201
15202             // now append the newline and continuation sequence
15203             if (dest)
15204                 dest[len] = '\n';
15205             len++;
15206             if (dest)
15207                 strncpy(dest+len, cseq, cseq_len);
15208             len += cseq_len;
15209             line = cseq_len;
15210             clen = cseq_len;
15211             continue;
15212         }
15213
15214         if (dest)
15215             dest[len] = src[i];
15216         len++;
15217         if (!ansi)
15218             line++;
15219         if (src[i] == '\n')
15220             line = 0;
15221         if (src[i] == 'm')
15222             ansi = 0;
15223     }
15224     if (dest && appData.debugMode)
15225     {
15226         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15227             count, width, line, len, *lp);
15228         show_bytes(debugFP, src, count);
15229         fprintf(debugFP, "\ndest: ");
15230         show_bytes(debugFP, dest, len);
15231         fprintf(debugFP, "\n");
15232     }
15233     *lp = dest ? line : old_line;
15234
15235     return len;
15236 }
15237
15238 // [HGM] vari: routines for shelving variations
15239
15240 void
15241 PushTail(int firstMove, int lastMove)
15242 {
15243         int i, j, nrMoves = lastMove - firstMove;
15244
15245         if(appData.icsActive) { // only in local mode
15246                 forwardMostMove = currentMove; // mimic old ICS behavior
15247                 return;
15248         }
15249         if(storedGames >= MAX_VARIATIONS-1) return;
15250
15251         // push current tail of game on stack
15252         savedResult[storedGames] = gameInfo.result;
15253         savedDetails[storedGames] = gameInfo.resultDetails;
15254         gameInfo.resultDetails = NULL;
15255         savedFirst[storedGames] = firstMove;
15256         savedLast [storedGames] = lastMove;
15257         savedFramePtr[storedGames] = framePtr;
15258         framePtr -= nrMoves; // reserve space for the boards
15259         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15260             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15261             for(j=0; j<MOVE_LEN; j++)
15262                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15263             for(j=0; j<2*MOVE_LEN; j++)
15264                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15265             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15266             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15267             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15268             pvInfoList[firstMove+i-1].depth = 0;
15269             commentList[framePtr+i] = commentList[firstMove+i];
15270             commentList[firstMove+i] = NULL;
15271         }
15272
15273         storedGames++;
15274         forwardMostMove = firstMove; // truncate game so we can start variation
15275         if(storedGames == 1) GreyRevert(FALSE);
15276 }
15277
15278 Boolean
15279 PopTail(Boolean annotate)
15280 {
15281         int i, j, nrMoves;
15282         char buf[8000], moveBuf[20];
15283
15284         if(appData.icsActive) return FALSE; // only in local mode
15285         if(!storedGames) return FALSE; // sanity
15286         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15287
15288         storedGames--;
15289         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15290         nrMoves = savedLast[storedGames] - currentMove;
15291         if(annotate) {
15292                 int cnt = 10;
15293                 if(!WhiteOnMove(currentMove))
15294                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15295                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15296                 for(i=currentMove; i<forwardMostMove; i++) {
15297                         if(WhiteOnMove(i))
15298                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15299                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15300                         strcat(buf, moveBuf);
15301                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15302                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15303                 }
15304                 strcat(buf, ")");
15305         }
15306         for(i=1; i<=nrMoves; i++) { // copy last variation back
15307             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15308             for(j=0; j<MOVE_LEN; j++)
15309                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15310             for(j=0; j<2*MOVE_LEN; j++)
15311                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15312             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15313             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15314             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15315             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15316             commentList[currentMove+i] = commentList[framePtr+i];
15317             commentList[framePtr+i] = NULL;
15318         }
15319         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15320         framePtr = savedFramePtr[storedGames];
15321         gameInfo.result = savedResult[storedGames];
15322         if(gameInfo.resultDetails != NULL) {
15323             free(gameInfo.resultDetails);
15324       }
15325         gameInfo.resultDetails = savedDetails[storedGames];
15326         forwardMostMove = currentMove + nrMoves;
15327         if(storedGames == 0) GreyRevert(TRUE);
15328         return TRUE;
15329 }
15330
15331 void
15332 CleanupTail()
15333 {       // remove all shelved variations
15334         int i;
15335         for(i=0; i<storedGames; i++) {
15336             if(savedDetails[i])
15337                 free(savedDetails[i]);
15338             savedDetails[i] = NULL;
15339         }
15340         for(i=framePtr; i<MAX_MOVES; i++) {
15341                 if(commentList[i]) free(commentList[i]);
15342                 commentList[i] = NULL;
15343         }
15344         framePtr = MAX_MOVES-1;
15345         storedGames = 0;
15346 }
15347
15348 void
15349 LoadVariation(int index, char *text)
15350 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15351         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15352         int level = 0, move;
15353
15354         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15355         // first find outermost bracketing variation
15356         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15357             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15358                 if(*p == '{') wait = '}'; else
15359                 if(*p == '[') wait = ']'; else
15360                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15361                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15362             }
15363             if(*p == wait) wait = NULLCHAR; // closing ]} found
15364             p++;
15365         }
15366         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15367         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15368         end[1] = NULLCHAR; // clip off comment beyond variation
15369         ToNrEvent(currentMove-1);
15370         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15371         // kludge: use ParsePV() to append variation to game
15372         move = currentMove;
15373         ParsePV(start, TRUE);
15374         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15375         ClearPremoveHighlights();
15376         CommentPopDown();
15377         ToNrEvent(currentMove+1);
15378 }
15379