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