Let -oneClickMove also work in EditGame mode
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 {
315   /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
316    *
317    * usage:   safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
318    */
319
320   assert( dst != NULL );
321   assert( src != NULL );
322   assert( count > 0 );
323
324   strncpy( dst, src, count );
325   if(  dst[ count-1 ] != '\0' )
326     {
327       if(appData.debugMode)
328       printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
329     }
330   dst[ count-1 ] = '\0';
331
332   return dst;
333 }
334
335 /* Some compiler can't cast u64 to double
336  * This function do the job for us:
337
338  * We use the highest bit for cast, this only
339  * works if the highest bit is not
340  * in use (This should not happen)
341  *
342  * We used this for all compiler
343  */
344 double
345 u64ToDouble(u64 value)
346 {
347   double r;
348   u64 tmp = value & u64Const(0x7fffffffffffffff);
349   r = (double)(s64)tmp;
350   if (value & u64Const(0x8000000000000000))
351        r +=  9.2233720368547758080e18; /* 2^63 */
352  return r;
353 }
354
355 /* Fake up flags for now, as we aren't keeping track of castling
356    availability yet. [HGM] Change of logic: the flag now only
357    indicates the type of castlings allowed by the rule of the game.
358    The actual rights themselves are maintained in the array
359    castlingRights, as part of the game history, and are not probed
360    by this function.
361  */
362 int
363 PosFlags(index)
364 {
365   int flags = F_ALL_CASTLE_OK;
366   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367   switch (gameInfo.variant) {
368   case VariantSuicide:
369     flags &= ~F_ALL_CASTLE_OK;
370   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371     flags |= F_IGNORE_CHECK;
372   case VariantLosers:
373     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374     break;
375   case VariantAtomic:
376     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377     break;
378   case VariantKriegspiel:
379     flags |= F_KRIEGSPIEL_CAPTURE;
380     break;
381   case VariantCapaRandom:
382   case VariantFischeRandom:
383     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384   case VariantNoCastle:
385   case VariantShatranj:
386   case VariantCourier:
387   case VariantMakruk:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP;
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
453
454 int have_sent_ICS_logon = 0;
455 int sending_ICS_login    = 0;
456 int sending_ICS_password = 0;
457
458 int movesPerSession;
459 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
460 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
461 long timeControl_2; /* [AS] Allow separate time controls */
462 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
463 long timeRemaining[2][MAX_MOVES];
464 int matchGame = 0;
465 TimeMark programStartTime;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 int shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
530         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
531 };
532
533 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
534     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
535         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
536     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
537         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
538 };
539
540 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
542         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackMan, BlackFerz,
544         BlackKing, BlackMan, BlackKnight, BlackRook }
545 };
546
547
548 #if (BOARD_FILES>=10)
549 ChessSquare ShogiArray[2][BOARD_FILES] = {
550     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
551         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
552     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
553         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
554 };
555
556 ChessSquare XiangqiArray[2][BOARD_FILES] = {
557     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
558         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
559     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
560         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
561 };
562
563 ChessSquare CapablancaArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
565         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
567         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
568 };
569
570 ChessSquare GreatArray[2][BOARD_FILES] = {
571     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
572         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
573     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
574         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
575 };
576
577 ChessSquare JanusArray[2][BOARD_FILES] = {
578     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
579         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
580     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
581         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
582 };
583
584 #ifdef GOTHIC
585 ChessSquare GothicArray[2][BOARD_FILES] = {
586     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
587         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
588     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
589         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
590 };
591 #else // !GOTHIC
592 #define GothicArray CapablancaArray
593 #endif // !GOTHIC
594
595 #ifdef FALCON
596 ChessSquare FalconArray[2][BOARD_FILES] = {
597     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
598         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
599     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
600         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
601 };
602 #else // !FALCON
603 #define FalconArray CapablancaArray
604 #endif // !FALCON
605
606 #else // !(BOARD_FILES>=10)
607 #define XiangqiPosition FIDEArray
608 #define CapablancaArray FIDEArray
609 #define GothicArray FIDEArray
610 #define GreatArray FIDEArray
611 #endif // !(BOARD_FILES>=10)
612
613 #if (BOARD_FILES>=12)
614 ChessSquare CourierArray[2][BOARD_FILES] = {
615     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
616         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
617     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
618         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
619 };
620 #else // !(BOARD_FILES>=12)
621 #define CourierArray CapablancaArray
622 #endif // !(BOARD_FILES>=12)
623
624
625 Board initialPosition;
626
627
628 /* Convert str to a rating. Checks for special cases of "----",
629
630    "++++", etc. Also strips ()'s */
631 int
632 string_to_rating(str)
633   char *str;
634 {
635   while(*str && !isdigit(*str)) ++str;
636   if (!*str)
637     return 0;   /* One of the special "no rating" cases */
638   else
639     return atoi(str);
640 }
641
642 void
643 ClearProgramStats()
644 {
645     /* Init programStats */
646     programStats.movelist[0] = 0;
647     programStats.depth = 0;
648     programStats.nr_moves = 0;
649     programStats.moves_left = 0;
650     programStats.nodes = 0;
651     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
652     programStats.score = 0;
653     programStats.got_only_move = 0;
654     programStats.got_fail = 0;
655     programStats.line_is_book = 0;
656 }
657
658 void
659 InitBackEnd1()
660 {
661     int matched, min, sec;
662
663     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
664     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
665
666     GetTimeMark(&programStartTime);
667     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
668
669     ClearProgramStats();
670     programStats.ok_to_send = 1;
671     programStats.seen_stat = 0;
672
673     /*
674      * Initialize game list
675      */
676     ListNew(&gameList);
677
678
679     /*
680      * Internet chess server status
681      */
682     if (appData.icsActive) {
683         appData.matchMode = FALSE;
684         appData.matchGames = 0;
685 #if ZIPPY
686         appData.noChessProgram = !appData.zippyPlay;
687 #else
688         appData.zippyPlay = FALSE;
689         appData.zippyTalk = FALSE;
690         appData.noChessProgram = TRUE;
691 #endif
692         if (*appData.icsHelper != NULLCHAR) {
693             appData.useTelnet = TRUE;
694             appData.telnetProgram = appData.icsHelper;
695         }
696     } else {
697         appData.zippyTalk = appData.zippyPlay = FALSE;
698     }
699
700     /* [AS] Initialize pv info list [HGM] and game state */
701     {
702         int i, j;
703
704         for( i=0; i<=framePtr; i++ ) {
705             pvInfoList[i].depth = -1;
706             boards[i][EP_STATUS] = EP_NONE;
707             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
708         }
709     }
710
711     /*
712      * Parse timeControl resource
713      */
714     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
715                           appData.movesPerSession)) {
716         char buf[MSG_SIZ];
717         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
718         DisplayFatalError(buf, 0, 2);
719     }
720
721     /*
722      * Parse searchTime resource
723      */
724     if (*appData.searchTime != NULLCHAR) {
725         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
726         if (matched == 1) {
727             searchTime = min * 60;
728         } else if (matched == 2) {
729             searchTime = min * 60 + sec;
730         } else {
731             char buf[MSG_SIZ];
732             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
733             DisplayFatalError(buf, 0, 2);
734         }
735     }
736
737     /* [AS] Adjudication threshold */
738     adjudicateLossThreshold = appData.adjudicateLossThreshold;
739
740     first.which = _("first");
741     second.which = _("second");
742     first.maybeThinking = second.maybeThinking = FALSE;
743     first.pr = second.pr = NoProc;
744     first.isr = second.isr = NULL;
745     first.sendTime = second.sendTime = 2;
746     first.sendDrawOffers = 1;
747     if (appData.firstPlaysBlack) {
748         first.twoMachinesColor = "black\n";
749         second.twoMachinesColor = "white\n";
750     } else {
751         first.twoMachinesColor = "white\n";
752         second.twoMachinesColor = "black\n";
753     }
754     first.program = appData.firstChessProgram;
755     second.program = appData.secondChessProgram;
756     first.host = appData.firstHost;
757     second.host = appData.secondHost;
758     first.dir = appData.firstDirectory;
759     second.dir = appData.secondDirectory;
760     first.other = &second;
761     second.other = &first;
762     first.initString = appData.initString;
763     second.initString = appData.secondInitString;
764     first.computerString = appData.firstComputerString;
765     second.computerString = appData.secondComputerString;
766     first.useSigint = second.useSigint = TRUE;
767     first.useSigterm = second.useSigterm = TRUE;
768     first.reuse = appData.reuseFirst;
769     second.reuse = appData.reuseSecond;
770     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
771     second.nps = appData.secondNPS;
772     first.useSetboard = second.useSetboard = FALSE;
773     first.useSAN = second.useSAN = FALSE;
774     first.usePing = second.usePing = FALSE;
775     first.lastPing = second.lastPing = 0;
776     first.lastPong = second.lastPong = 0;
777     first.usePlayother = second.usePlayother = FALSE;
778     first.useColors = second.useColors = TRUE;
779     first.useUsermove = second.useUsermove = FALSE;
780     first.sendICS = second.sendICS = FALSE;
781     first.sendName = second.sendName = appData.icsActive;
782     first.sdKludge = second.sdKludge = FALSE;
783     first.stKludge = second.stKludge = FALSE;
784     TidyProgramName(first.program, first.host, first.tidy);
785     TidyProgramName(second.program, second.host, second.tidy);
786     first.matchWins = second.matchWins = 0;
787     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
788     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
789     first.analysisSupport = second.analysisSupport = 2; /* detect */
790     first.analyzing = second.analyzing = FALSE;
791     first.initDone = second.initDone = FALSE;
792
793     /* New features added by Tord: */
794     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
795     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
796     /* End of new features added by Tord. */
797     first.fenOverride  = appData.fenOverride1;
798     second.fenOverride = appData.fenOverride2;
799
800     /* [HGM] time odds: set factor for each machine */
801     first.timeOdds  = appData.firstTimeOdds;
802     second.timeOdds = appData.secondTimeOdds;
803     { float norm = 1;
804         if(appData.timeOddsMode) {
805             norm = first.timeOdds;
806             if(norm > second.timeOdds) norm = second.timeOdds;
807         }
808         first.timeOdds /= norm;
809         second.timeOdds /= norm;
810     }
811
812     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
813     first.accumulateTC = appData.firstAccumulateTC;
814     second.accumulateTC = appData.secondAccumulateTC;
815     first.maxNrOfSessions = second.maxNrOfSessions = 1;
816
817     /* [HGM] debug */
818     first.debug = second.debug = FALSE;
819     first.supportsNPS = second.supportsNPS = UNKNOWN;
820
821     /* [HGM] options */
822     first.optionSettings  = appData.firstOptions;
823     second.optionSettings = appData.secondOptions;
824
825     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
826     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
827     first.isUCI = appData.firstIsUCI; /* [AS] */
828     second.isUCI = appData.secondIsUCI; /* [AS] */
829     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
830     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
831
832     if (appData.firstProtocolVersion > PROTOVER
833         || appData.firstProtocolVersion < 1)
834       {
835         char buf[MSG_SIZ];
836         int len;
837
838         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
839                        appData.firstProtocolVersion);
840         if( (len > MSG_SIZ) && appData.debugMode )
841           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842
843         DisplayFatalError(buf, 0, 2);
844       }
845     else
846       {
847         first.protocolVersion = appData.firstProtocolVersion;
848       }
849
850     if (appData.secondProtocolVersion > PROTOVER
851         || appData.secondProtocolVersion < 1)
852       {
853         char buf[MSG_SIZ];
854         int len;
855
856         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
857                        appData.secondProtocolVersion);
858         if( (len > MSG_SIZ) && appData.debugMode )
859           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
860
861         DisplayFatalError(buf, 0, 2);
862       }
863     else
864       {
865         second.protocolVersion = appData.secondProtocolVersion;
866       }
867
868     if (appData.icsActive) {
869         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
870 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
871     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
872         appData.clockMode = FALSE;
873         first.sendTime = second.sendTime = 0;
874     }
875
876 #if ZIPPY
877     /* Override some settings from environment variables, for backward
878        compatibility.  Unfortunately it's not feasible to have the env
879        vars just set defaults, at least in xboard.  Ugh.
880     */
881     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
882       ZippyInit();
883     }
884 #endif
885
886     if (appData.noChessProgram) {
887         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
888         sprintf(programVersion, "%s", PACKAGE_STRING);
889     } else {
890       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
891       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
892       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
893     }
894
895     if (!appData.icsActive) {
896       char buf[MSG_SIZ];
897       int len;
898
899       /* Check for variants that are supported only in ICS mode,
900          or not at all.  Some that are accepted here nevertheless
901          have bugs; see comments below.
902       */
903       VariantClass variant = StringToVariant(appData.variant);
904       switch (variant) {
905       case VariantBughouse:     /* need four players and two boards */
906       case VariantKriegspiel:   /* need to hide pieces and move details */
907         /* case VariantFischeRandom: (Fabien: moved below) */
908         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
909         if( (len > MSG_SIZ) && appData.debugMode )
910           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
911
912         DisplayFatalError(buf, 0, 2);
913         return;
914
915       case VariantUnknown:
916       case VariantLoadable:
917       case Variant29:
918       case Variant30:
919       case Variant31:
920       case Variant32:
921       case Variant33:
922       case Variant34:
923       case Variant35:
924       case Variant36:
925       default:
926         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
927         if( (len > MSG_SIZ) && appData.debugMode )
928           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
929
930         DisplayFatalError(buf, 0, 2);
931         return;
932
933       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
934       case VariantFairy:      /* [HGM] TestLegality definitely off! */
935       case VariantGothic:     /* [HGM] should work */
936       case VariantCapablanca: /* [HGM] should work */
937       case VariantCourier:    /* [HGM] initial forced moves not implemented */
938       case VariantShogi:      /* [HGM] could still mate with pawn drop */
939       case VariantKnightmate: /* [HGM] should work */
940       case VariantCylinder:   /* [HGM] untested */
941       case VariantFalcon:     /* [HGM] untested */
942       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
943                                  offboard interposition not understood */
944       case VariantNormal:     /* definitely works! */
945       case VariantWildCastle: /* pieces not automatically shuffled */
946       case VariantNoCastle:   /* pieces not automatically shuffled */
947       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
948       case VariantLosers:     /* should work except for win condition,
949                                  and doesn't know captures are mandatory */
950       case VariantSuicide:    /* should work except for win condition,
951                                  and doesn't know captures are mandatory */
952       case VariantGiveaway:   /* should work except for win condition,
953                                  and doesn't know captures are mandatory */
954       case VariantTwoKings:   /* should work */
955       case VariantAtomic:     /* should work except for win condition */
956       case Variant3Check:     /* should work except for win condition */
957       case VariantShatranj:   /* should work except for all win conditions */
958       case VariantMakruk:     /* should work except for daw countdown */
959       case VariantBerolina:   /* might work if TestLegality is off */
960       case VariantCapaRandom: /* should work */
961       case VariantJanus:      /* should work */
962       case VariantSuper:      /* experimental */
963       case VariantGreat:      /* experimental, requires legality testing to be off */
964         break;
965       }
966     }
967
968     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
969     InitEngineUCI( installDir, &second );
970 }
971
972 int NextIntegerFromString( char ** str, long * value )
973 {
974     int result = -1;
975     char * s = *str;
976
977     while( *s == ' ' || *s == '\t' ) {
978         s++;
979     }
980
981     *value = 0;
982
983     if( *s >= '0' && *s <= '9' ) {
984         while( *s >= '0' && *s <= '9' ) {
985             *value = *value * 10 + (*s - '0');
986             s++;
987         }
988
989         result = 0;
990     }
991
992     *str = s;
993
994     return result;
995 }
996
997 int NextTimeControlFromString( char ** str, long * value )
998 {
999     long temp;
1000     int result = NextIntegerFromString( str, &temp );
1001
1002     if( result == 0 ) {
1003         *value = temp * 60; /* Minutes */
1004         if( **str == ':' ) {
1005             (*str)++;
1006             result = NextIntegerFromString( str, &temp );
1007             *value += temp; /* Seconds */
1008         }
1009     }
1010
1011     return result;
1012 }
1013
1014 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1015 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1016     int result = -1, type = 0; long temp, temp2;
1017
1018     if(**str != ':') return -1; // old params remain in force!
1019     (*str)++;
1020     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1021     if( NextIntegerFromString( str, &temp ) ) return -1;
1022     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1023
1024     if(**str != '/') {
1025         /* time only: incremental or sudden-death time control */
1026         if(**str == '+') { /* increment follows; read it */
1027             (*str)++;
1028             if(**str == '!') type = *(*str)++; // Bronstein TC
1029             if(result = NextIntegerFromString( str, &temp2)) return -1;
1030             *inc = temp2 * 1000;
1031             if(**str == '.') { // read fraction of increment
1032                 char *start = ++(*str);
1033                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1034                 temp2 *= 1000;
1035                 while(start++ < *str) temp2 /= 10;
1036                 *inc += temp2;
1037             }
1038         } else *inc = 0;
1039         *moves = 0; *tc = temp * 1000; *incType = type;
1040         return 0;
1041     }
1042
1043     (*str)++; /* classical time control */
1044     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1045
1046     if(result == 0) {
1047         *moves = temp;
1048         *tc    = temp2 * 1000;
1049         *inc   = 0;
1050         *incType = type;
1051     }
1052     return result;
1053 }
1054
1055 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1056 {   /* [HGM] get time to add from the multi-session time-control string */
1057     int incType, moves=1; /* kludge to force reading of first session */
1058     long time, increment;
1059     char *s = tcString;
1060
1061     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1062     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1063     do {
1064         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1065         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1066         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1067         if(movenr == -1) return time;    /* last move before new session     */
1068         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1069         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1070         if(!moves) return increment;     /* current session is incremental   */
1071         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1072     } while(movenr >= -1);               /* try again for next session       */
1073
1074     return 0; // no new time quota on this move
1075 }
1076
1077 int
1078 ParseTimeControl(tc, ti, mps)
1079      char *tc;
1080      float ti;
1081      int mps;
1082 {
1083   long tc1;
1084   long tc2;
1085   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1086   int min, sec=0;
1087
1088   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1089   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1090       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1091   if(ti > 0) {
1092
1093     if(mps)
1094       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1095     else
1096       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1097   } else {
1098     if(mps)
1099       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1100     else
1101       snprintf(buf, MSG_SIZ, ":%s", mytc);
1102   }
1103   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1104
1105   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1106     return FALSE;
1107   }
1108
1109   if( *tc == '/' ) {
1110     /* Parse second time control */
1111     tc++;
1112
1113     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1114       return FALSE;
1115     }
1116
1117     if( tc2 == 0 ) {
1118       return FALSE;
1119     }
1120
1121     timeControl_2 = tc2 * 1000;
1122   }
1123   else {
1124     timeControl_2 = 0;
1125   }
1126
1127   if( tc1 == 0 ) {
1128     return FALSE;
1129   }
1130
1131   timeControl = tc1 * 1000;
1132
1133   if (ti >= 0) {
1134     timeIncrement = ti * 1000;  /* convert to ms */
1135     movesPerSession = 0;
1136   } else {
1137     timeIncrement = 0;
1138     movesPerSession = mps;
1139   }
1140   return TRUE;
1141 }
1142
1143 void
1144 InitBackEnd2()
1145 {
1146     if (appData.debugMode) {
1147         fprintf(debugFP, "%s\n", programVersion);
1148     }
1149
1150     set_cont_sequence(appData.wrapContSeq);
1151     if (appData.matchGames > 0) {
1152         appData.matchMode = TRUE;
1153     } else if (appData.matchMode) {
1154         appData.matchGames = 1;
1155     }
1156     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1157         appData.matchGames = appData.sameColorGames;
1158     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1159         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1160         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1161     }
1162     Reset(TRUE, FALSE);
1163     if (appData.noChessProgram || first.protocolVersion == 1) {
1164       InitBackEnd3();
1165     } else {
1166       /* kludge: allow timeout for initial "feature" commands */
1167       FreezeUI();
1168       DisplayMessage("", _("Starting chess program"));
1169       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1170     }
1171 }
1172
1173 void
1174 InitBackEnd3 P((void))
1175 {
1176     GameMode initialMode;
1177     char buf[MSG_SIZ];
1178     int err, len;
1179
1180     InitChessProgram(&first, startedFromSetupPosition);
1181
1182     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1183         free(programVersion);
1184         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1185         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1186     }
1187
1188     if (appData.icsActive) {
1189 #ifdef WIN32
1190         /* [DM] Make a console window if needed [HGM] merged ifs */
1191         ConsoleCreate();
1192 #endif
1193         err = establish();
1194         if (err != 0)
1195           {
1196             if (*appData.icsCommPort != NULLCHAR)
1197               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1198                              appData.icsCommPort);
1199             else
1200               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1201                         appData.icsHost, appData.icsPort);
1202
1203             if( (len > MSG_SIZ) && appData.debugMode )
1204               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1205
1206             DisplayFatalError(buf, err, 1);
1207             return;
1208         }
1209         SetICSMode();
1210         telnetISR =
1211           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1212         fromUserISR =
1213           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1214         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1215             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1216     } else if (appData.noChessProgram) {
1217         SetNCPMode();
1218     } else {
1219         SetGNUMode();
1220     }
1221
1222     if (*appData.cmailGameName != NULLCHAR) {
1223         SetCmailMode();
1224         OpenLoopback(&cmailPR);
1225         cmailISR =
1226           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1227     }
1228
1229     ThawUI();
1230     DisplayMessage("", "");
1231     if (StrCaseCmp(appData.initialMode, "") == 0) {
1232       initialMode = BeginningOfGame;
1233     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1234       initialMode = TwoMachinesPlay;
1235     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1236       initialMode = AnalyzeFile;
1237     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1238       initialMode = AnalyzeMode;
1239     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1240       initialMode = MachinePlaysWhite;
1241     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1242       initialMode = MachinePlaysBlack;
1243     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1244       initialMode = EditGame;
1245     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1246       initialMode = EditPosition;
1247     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1248       initialMode = Training;
1249     } else {
1250       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1251       if( (len > MSG_SIZ) && appData.debugMode )
1252         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1253
1254       DisplayFatalError(buf, 0, 2);
1255       return;
1256     }
1257
1258     if (appData.matchMode) {
1259         /* Set up machine vs. machine match */
1260         if (appData.noChessProgram) {
1261             DisplayFatalError(_("Can't have a match with no chess programs"),
1262                               0, 2);
1263             return;
1264         }
1265         matchMode = TRUE;
1266         matchGame = 1;
1267         if (*appData.loadGameFile != NULLCHAR) {
1268             int index = appData.loadGameIndex; // [HGM] autoinc
1269             if(index<0) lastIndex = index = 1;
1270             if (!LoadGameFromFile(appData.loadGameFile,
1271                                   index,
1272                                   appData.loadGameFile, FALSE)) {
1273                 DisplayFatalError(_("Bad game file"), 0, 1);
1274                 return;
1275             }
1276         } else if (*appData.loadPositionFile != NULLCHAR) {
1277             int index = appData.loadPositionIndex; // [HGM] autoinc
1278             if(index<0) lastIndex = index = 1;
1279             if (!LoadPositionFromFile(appData.loadPositionFile,
1280                                       index,
1281                                       appData.loadPositionFile)) {
1282                 DisplayFatalError(_("Bad position file"), 0, 1);
1283                 return;
1284             }
1285         }
1286         TwoMachinesEvent();
1287     } else if (*appData.cmailGameName != NULLCHAR) {
1288         /* Set up cmail mode */
1289         ReloadCmailMsgEvent(TRUE);
1290     } else {
1291         /* Set up other modes */
1292         if (initialMode == AnalyzeFile) {
1293           if (*appData.loadGameFile == NULLCHAR) {
1294             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1295             return;
1296           }
1297         }
1298         if (*appData.loadGameFile != NULLCHAR) {
1299             (void) LoadGameFromFile(appData.loadGameFile,
1300                                     appData.loadGameIndex,
1301                                     appData.loadGameFile, TRUE);
1302         } else if (*appData.loadPositionFile != NULLCHAR) {
1303             (void) LoadPositionFromFile(appData.loadPositionFile,
1304                                         appData.loadPositionIndex,
1305                                         appData.loadPositionFile);
1306             /* [HGM] try to make self-starting even after FEN load */
1307             /* to allow automatic setup of fairy variants with wtm */
1308             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1309                 gameMode = BeginningOfGame;
1310                 setboardSpoiledMachineBlack = 1;
1311             }
1312             /* [HGM] loadPos: make that every new game uses the setup */
1313             /* from file as long as we do not switch variant          */
1314             if(!blackPlaysFirst) {
1315                 startedFromPositionFile = TRUE;
1316                 CopyBoard(filePosition, boards[0]);
1317             }
1318         }
1319         if (initialMode == AnalyzeMode) {
1320           if (appData.noChessProgram) {
1321             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1322             return;
1323           }
1324           if (appData.icsActive) {
1325             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1326             return;
1327           }
1328           AnalyzeModeEvent();
1329         } else if (initialMode == AnalyzeFile) {
1330           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1331           ShowThinkingEvent();
1332           AnalyzeFileEvent();
1333           AnalysisPeriodicEvent(1);
1334         } else if (initialMode == MachinePlaysWhite) {
1335           if (appData.noChessProgram) {
1336             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1337                               0, 2);
1338             return;
1339           }
1340           if (appData.icsActive) {
1341             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1342                               0, 2);
1343             return;
1344           }
1345           MachineWhiteEvent();
1346         } else if (initialMode == MachinePlaysBlack) {
1347           if (appData.noChessProgram) {
1348             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1349                               0, 2);
1350             return;
1351           }
1352           if (appData.icsActive) {
1353             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1354                               0, 2);
1355             return;
1356           }
1357           MachineBlackEvent();
1358         } else if (initialMode == TwoMachinesPlay) {
1359           if (appData.noChessProgram) {
1360             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1361                               0, 2);
1362             return;
1363           }
1364           if (appData.icsActive) {
1365             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1366                               0, 2);
1367             return;
1368           }
1369           TwoMachinesEvent();
1370         } else if (initialMode == EditGame) {
1371           EditGameEvent();
1372         } else if (initialMode == EditPosition) {
1373           EditPositionEvent();
1374         } else if (initialMode == Training) {
1375           if (*appData.loadGameFile == NULLCHAR) {
1376             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1377             return;
1378           }
1379           TrainingEvent();
1380         }
1381     }
1382 }
1383
1384 /*
1385  * Establish will establish a contact to a remote host.port.
1386  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1387  *  used to talk to the host.
1388  * Returns 0 if okay, error code if not.
1389  */
1390 int
1391 establish()
1392 {
1393     char buf[MSG_SIZ];
1394
1395     if (*appData.icsCommPort != NULLCHAR) {
1396         /* Talk to the host through a serial comm port */
1397         return OpenCommPort(appData.icsCommPort, &icsPR);
1398
1399     } else if (*appData.gateway != NULLCHAR) {
1400         if (*appData.remoteShell == NULLCHAR) {
1401             /* Use the rcmd protocol to run telnet program on a gateway host */
1402             snprintf(buf, sizeof(buf), "%s %s %s",
1403                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1404             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1405
1406         } else {
1407             /* Use the rsh program to run telnet program on a gateway host */
1408             if (*appData.remoteUser == NULLCHAR) {
1409                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1410                         appData.gateway, appData.telnetProgram,
1411                         appData.icsHost, appData.icsPort);
1412             } else {
1413                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1414                         appData.remoteShell, appData.gateway,
1415                         appData.remoteUser, appData.telnetProgram,
1416                         appData.icsHost, appData.icsPort);
1417             }
1418             return StartChildProcess(buf, "", &icsPR);
1419
1420         }
1421     } else if (appData.useTelnet) {
1422         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1423
1424     } else {
1425         /* TCP socket interface differs somewhat between
1426            Unix and NT; handle details in the front end.
1427            */
1428         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1429     }
1430 }
1431
1432 void EscapeExpand(char *p, char *q)
1433 {       // [HGM] initstring: routine to shape up string arguments
1434         while(*p++ = *q++) if(p[-1] == '\\')
1435             switch(*q++) {
1436                 case 'n': p[-1] = '\n'; break;
1437                 case 'r': p[-1] = '\r'; break;
1438                 case 't': p[-1] = '\t'; break;
1439                 case '\\': p[-1] = '\\'; break;
1440                 case 0: *p = 0; return;
1441                 default: p[-1] = q[-1]; break;
1442             }
1443 }
1444
1445 void
1446 show_bytes(fp, buf, count)
1447      FILE *fp;
1448      char *buf;
1449      int count;
1450 {
1451     while (count--) {
1452         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1453             fprintf(fp, "\\%03o", *buf & 0xff);
1454         } else {
1455             putc(*buf, fp);
1456         }
1457         buf++;
1458     }
1459     fflush(fp);
1460 }
1461
1462 /* Returns an errno value */
1463 int
1464 OutputMaybeTelnet(pr, message, count, outError)
1465      ProcRef pr;
1466      char *message;
1467      int count;
1468      int *outError;
1469 {
1470     char buf[8192], *p, *q, *buflim;
1471     int left, newcount, outcount;
1472
1473     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1474         *appData.gateway != NULLCHAR) {
1475         if (appData.debugMode) {
1476             fprintf(debugFP, ">ICS: ");
1477             show_bytes(debugFP, message, count);
1478             fprintf(debugFP, "\n");
1479         }
1480         return OutputToProcess(pr, message, count, outError);
1481     }
1482
1483     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1484     p = message;
1485     q = buf;
1486     left = count;
1487     newcount = 0;
1488     while (left) {
1489         if (q >= buflim) {
1490             if (appData.debugMode) {
1491                 fprintf(debugFP, ">ICS: ");
1492                 show_bytes(debugFP, buf, newcount);
1493                 fprintf(debugFP, "\n");
1494             }
1495             outcount = OutputToProcess(pr, buf, newcount, outError);
1496             if (outcount < newcount) return -1; /* to be sure */
1497             q = buf;
1498             newcount = 0;
1499         }
1500         if (*p == '\n') {
1501             *q++ = '\r';
1502             newcount++;
1503         } else if (((unsigned char) *p) == TN_IAC) {
1504             *q++ = (char) TN_IAC;
1505             newcount ++;
1506         }
1507         *q++ = *p++;
1508         newcount++;
1509         left--;
1510     }
1511     if (appData.debugMode) {
1512         fprintf(debugFP, ">ICS: ");
1513         show_bytes(debugFP, buf, newcount);
1514         fprintf(debugFP, "\n");
1515     }
1516     outcount = OutputToProcess(pr, buf, newcount, outError);
1517     if (outcount < newcount) return -1; /* to be sure */
1518     return count;
1519 }
1520
1521 void
1522 read_from_player(isr, closure, message, count, error)
1523      InputSourceRef isr;
1524      VOIDSTAR closure;
1525      char *message;
1526      int count;
1527      int error;
1528 {
1529     int outError, outCount;
1530     static int gotEof = 0;
1531
1532     /* Pass data read from player on to ICS */
1533     if (count > 0) {
1534         gotEof = 0;
1535         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1536         if (outCount < count) {
1537             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1538         }
1539     } else if (count < 0) {
1540         RemoveInputSource(isr);
1541         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1542     } else if (gotEof++ > 0) {
1543         RemoveInputSource(isr);
1544         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1545     }
1546 }
1547
1548 void
1549 KeepAlive()
1550 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1551     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1552     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1553     SendToICS("date\n");
1554     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1555 }
1556
1557 /* added routine for printf style output to ics */
1558 void ics_printf(char *format, ...)
1559 {
1560     char buffer[MSG_SIZ];
1561     va_list args;
1562
1563     va_start(args, format);
1564     vsnprintf(buffer, sizeof(buffer), format, args);
1565     buffer[sizeof(buffer)-1] = '\0';
1566     SendToICS(buffer);
1567     va_end(args);
1568 }
1569
1570 void
1571 SendToICS(s)
1572      char *s;
1573 {
1574     int count, outCount, outError;
1575
1576     if (icsPR == NULL) return;
1577
1578     count = strlen(s);
1579     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1580     if (outCount < count) {
1581         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1582     }
1583 }
1584
1585 /* This is used for sending logon scripts to the ICS. Sending
1586    without a delay causes problems when using timestamp on ICC
1587    (at least on my machine). */
1588 void
1589 SendToICSDelayed(s,msdelay)
1590      char *s;
1591      long msdelay;
1592 {
1593     int count, outCount, outError;
1594
1595     if (icsPR == NULL) return;
1596
1597     count = strlen(s);
1598     if (appData.debugMode) {
1599         fprintf(debugFP, ">ICS: ");
1600         show_bytes(debugFP, s, count);
1601         fprintf(debugFP, "\n");
1602     }
1603     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1604                                       msdelay);
1605     if (outCount < count) {
1606         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1607     }
1608 }
1609
1610
1611 /* Remove all highlighting escape sequences in s
1612    Also deletes any suffix starting with '('
1613    */
1614 char *
1615 StripHighlightAndTitle(s)
1616      char *s;
1617 {
1618     static char retbuf[MSG_SIZ];
1619     char *p = retbuf;
1620
1621     while (*s != NULLCHAR) {
1622         while (*s == '\033') {
1623             while (*s != NULLCHAR && !isalpha(*s)) s++;
1624             if (*s != NULLCHAR) s++;
1625         }
1626         while (*s != NULLCHAR && *s != '\033') {
1627             if (*s == '(' || *s == '[') {
1628                 *p = NULLCHAR;
1629                 return retbuf;
1630             }
1631             *p++ = *s++;
1632         }
1633     }
1634     *p = NULLCHAR;
1635     return retbuf;
1636 }
1637
1638 /* Remove all highlighting escape sequences in s */
1639 char *
1640 StripHighlight(s)
1641      char *s;
1642 {
1643     static char retbuf[MSG_SIZ];
1644     char *p = retbuf;
1645
1646     while (*s != NULLCHAR) {
1647         while (*s == '\033') {
1648             while (*s != NULLCHAR && !isalpha(*s)) s++;
1649             if (*s != NULLCHAR) s++;
1650         }
1651         while (*s != NULLCHAR && *s != '\033') {
1652             *p++ = *s++;
1653         }
1654     }
1655     *p = NULLCHAR;
1656     return retbuf;
1657 }
1658
1659 char *variantNames[] = VARIANT_NAMES;
1660 char *
1661 VariantName(v)
1662      VariantClass v;
1663 {
1664     return variantNames[v];
1665 }
1666
1667
1668 /* Identify a variant from the strings the chess servers use or the
1669    PGN Variant tag names we use. */
1670 VariantClass
1671 StringToVariant(e)
1672      char *e;
1673 {
1674     char *p;
1675     int wnum = -1;
1676     VariantClass v = VariantNormal;
1677     int i, found = FALSE;
1678     char buf[MSG_SIZ];
1679     int len;
1680
1681     if (!e) return v;
1682
1683     /* [HGM] skip over optional board-size prefixes */
1684     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1685         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1686         while( *e++ != '_');
1687     }
1688
1689     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1690         v = VariantNormal;
1691         found = TRUE;
1692     } else
1693     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1694       if (StrCaseStr(e, variantNames[i])) {
1695         v = (VariantClass) i;
1696         found = TRUE;
1697         break;
1698       }
1699     }
1700
1701     if (!found) {
1702       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1703           || StrCaseStr(e, "wild/fr")
1704           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1705         v = VariantFischeRandom;
1706       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1707                  (i = 1, p = StrCaseStr(e, "w"))) {
1708         p += i;
1709         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1710         if (isdigit(*p)) {
1711           wnum = atoi(p);
1712         } else {
1713           wnum = -1;
1714         }
1715         switch (wnum) {
1716         case 0: /* FICS only, actually */
1717         case 1:
1718           /* Castling legal even if K starts on d-file */
1719           v = VariantWildCastle;
1720           break;
1721         case 2:
1722         case 3:
1723         case 4:
1724           /* Castling illegal even if K & R happen to start in
1725              normal positions. */
1726           v = VariantNoCastle;
1727           break;
1728         case 5:
1729         case 7:
1730         case 8:
1731         case 10:
1732         case 11:
1733         case 12:
1734         case 13:
1735         case 14:
1736         case 15:
1737         case 18:
1738         case 19:
1739           /* Castling legal iff K & R start in normal positions */
1740           v = VariantNormal;
1741           break;
1742         case 6:
1743         case 20:
1744         case 21:
1745           /* Special wilds for position setup; unclear what to do here */
1746           v = VariantLoadable;
1747           break;
1748         case 9:
1749           /* Bizarre ICC game */
1750           v = VariantTwoKings;
1751           break;
1752         case 16:
1753           v = VariantKriegspiel;
1754           break;
1755         case 17:
1756           v = VariantLosers;
1757           break;
1758         case 22:
1759           v = VariantFischeRandom;
1760           break;
1761         case 23:
1762           v = VariantCrazyhouse;
1763           break;
1764         case 24:
1765           v = VariantBughouse;
1766           break;
1767         case 25:
1768           v = Variant3Check;
1769           break;
1770         case 26:
1771           /* Not quite the same as FICS suicide! */
1772           v = VariantGiveaway;
1773           break;
1774         case 27:
1775           v = VariantAtomic;
1776           break;
1777         case 28:
1778           v = VariantShatranj;
1779           break;
1780
1781         /* Temporary names for future ICC types.  The name *will* change in
1782            the next xboard/WinBoard release after ICC defines it. */
1783         case 29:
1784           v = Variant29;
1785           break;
1786         case 30:
1787           v = Variant30;
1788           break;
1789         case 31:
1790           v = Variant31;
1791           break;
1792         case 32:
1793           v = Variant32;
1794           break;
1795         case 33:
1796           v = Variant33;
1797           break;
1798         case 34:
1799           v = Variant34;
1800           break;
1801         case 35:
1802           v = Variant35;
1803           break;
1804         case 36:
1805           v = Variant36;
1806           break;
1807         case 37:
1808           v = VariantShogi;
1809           break;
1810         case 38:
1811           v = VariantXiangqi;
1812           break;
1813         case 39:
1814           v = VariantCourier;
1815           break;
1816         case 40:
1817           v = VariantGothic;
1818           break;
1819         case 41:
1820           v = VariantCapablanca;
1821           break;
1822         case 42:
1823           v = VariantKnightmate;
1824           break;
1825         case 43:
1826           v = VariantFairy;
1827           break;
1828         case 44:
1829           v = VariantCylinder;
1830           break;
1831         case 45:
1832           v = VariantFalcon;
1833           break;
1834         case 46:
1835           v = VariantCapaRandom;
1836           break;
1837         case 47:
1838           v = VariantBerolina;
1839           break;
1840         case 48:
1841           v = VariantJanus;
1842           break;
1843         case 49:
1844           v = VariantSuper;
1845           break;
1846         case 50:
1847           v = VariantGreat;
1848           break;
1849         case -1:
1850           /* Found "wild" or "w" in the string but no number;
1851              must assume it's normal chess. */
1852           v = VariantNormal;
1853           break;
1854         default:
1855           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1856           if( (len > MSG_SIZ) && appData.debugMode )
1857             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1858
1859           DisplayError(buf, 0);
1860           v = VariantUnknown;
1861           break;
1862         }
1863       }
1864     }
1865     if (appData.debugMode) {
1866       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1867               e, wnum, VariantName(v));
1868     }
1869     return v;
1870 }
1871
1872 static int leftover_start = 0, leftover_len = 0;
1873 char star_match[STAR_MATCH_N][MSG_SIZ];
1874
1875 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1876    advance *index beyond it, and set leftover_start to the new value of
1877    *index; else return FALSE.  If pattern contains the character '*', it
1878    matches any sequence of characters not containing '\r', '\n', or the
1879    character following the '*' (if any), and the matched sequence(s) are
1880    copied into star_match.
1881    */
1882 int
1883 looking_at(buf, index, pattern)
1884      char *buf;
1885      int *index;
1886      char *pattern;
1887 {
1888     char *bufp = &buf[*index], *patternp = pattern;
1889     int star_count = 0;
1890     char *matchp = star_match[0];
1891
1892     for (;;) {
1893         if (*patternp == NULLCHAR) {
1894             *index = leftover_start = bufp - buf;
1895             *matchp = NULLCHAR;
1896             return TRUE;
1897         }
1898         if (*bufp == NULLCHAR) return FALSE;
1899         if (*patternp == '*') {
1900             if (*bufp == *(patternp + 1)) {
1901                 *matchp = NULLCHAR;
1902                 matchp = star_match[++star_count];
1903                 patternp += 2;
1904                 bufp++;
1905                 continue;
1906             } else if (*bufp == '\n' || *bufp == '\r') {
1907                 patternp++;
1908                 if (*patternp == NULLCHAR)
1909                   continue;
1910                 else
1911                   return FALSE;
1912             } else {
1913                 *matchp++ = *bufp++;
1914                 continue;
1915             }
1916         }
1917         if (*patternp != *bufp) return FALSE;
1918         patternp++;
1919         bufp++;
1920     }
1921 }
1922
1923 void
1924 SendToPlayer(data, length)
1925      char *data;
1926      int length;
1927 {
1928     int error, outCount;
1929     outCount = OutputToProcess(NoProc, data, length, &error);
1930     if (outCount < length) {
1931         DisplayFatalError(_("Error writing to display"), error, 1);
1932     }
1933 }
1934
1935 void
1936 PackHolding(packed, holding)
1937      char packed[];
1938      char *holding;
1939 {
1940     char *p = holding;
1941     char *q = packed;
1942     int runlength = 0;
1943     int curr = 9999;
1944     do {
1945         if (*p == curr) {
1946             runlength++;
1947         } else {
1948             switch (runlength) {
1949               case 0:
1950                 break;
1951               case 1:
1952                 *q++ = curr;
1953                 break;
1954               case 2:
1955                 *q++ = curr;
1956                 *q++ = curr;
1957                 break;
1958               default:
1959                 sprintf(q, "%d", runlength);
1960                 while (*q) q++;
1961                 *q++ = curr;
1962                 break;
1963             }
1964             runlength = 1;
1965             curr = *p;
1966         }
1967     } while (*p++);
1968     *q = NULLCHAR;
1969 }
1970
1971 /* Telnet protocol requests from the front end */
1972 void
1973 TelnetRequest(ddww, option)
1974      unsigned char ddww, option;
1975 {
1976     unsigned char msg[3];
1977     int outCount, outError;
1978
1979     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1980
1981     if (appData.debugMode) {
1982         char buf1[8], buf2[8], *ddwwStr, *optionStr;
1983         switch (ddww) {
1984           case TN_DO:
1985             ddwwStr = "DO";
1986             break;
1987           case TN_DONT:
1988             ddwwStr = "DONT";
1989             break;
1990           case TN_WILL:
1991             ddwwStr = "WILL";
1992             break;
1993           case TN_WONT:
1994             ddwwStr = "WONT";
1995             break;
1996           default:
1997             ddwwStr = buf1;
1998             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
1999             break;
2000         }
2001         switch (option) {
2002           case TN_ECHO:
2003             optionStr = "ECHO";
2004             break;
2005           default:
2006             optionStr = buf2;
2007             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2008             break;
2009         }
2010         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2011     }
2012     msg[0] = TN_IAC;
2013     msg[1] = ddww;
2014     msg[2] = option;
2015     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2016     if (outCount < 3) {
2017         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2018     }
2019 }
2020
2021 void
2022 DoEcho()
2023 {
2024     if (!appData.icsActive) return;
2025     TelnetRequest(TN_DO, TN_ECHO);
2026 }
2027
2028 void
2029 DontEcho()
2030 {
2031     if (!appData.icsActive) return;
2032     TelnetRequest(TN_DONT, TN_ECHO);
2033 }
2034
2035 void
2036 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2037 {
2038     /* put the holdings sent to us by the server on the board holdings area */
2039     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2040     char p;
2041     ChessSquare piece;
2042
2043     if(gameInfo.holdingsWidth < 2)  return;
2044     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2045         return; // prevent overwriting by pre-board holdings
2046
2047     if( (int)lowestPiece >= BlackPawn ) {
2048         holdingsColumn = 0;
2049         countsColumn = 1;
2050         holdingsStartRow = BOARD_HEIGHT-1;
2051         direction = -1;
2052     } else {
2053         holdingsColumn = BOARD_WIDTH-1;
2054         countsColumn = BOARD_WIDTH-2;
2055         holdingsStartRow = 0;
2056         direction = 1;
2057     }
2058
2059     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2060         board[i][holdingsColumn] = EmptySquare;
2061         board[i][countsColumn]   = (ChessSquare) 0;
2062     }
2063     while( (p=*holdings++) != NULLCHAR ) {
2064         piece = CharToPiece( ToUpper(p) );
2065         if(piece == EmptySquare) continue;
2066         /*j = (int) piece - (int) WhitePawn;*/
2067         j = PieceToNumber(piece);
2068         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2069         if(j < 0) continue;               /* should not happen */
2070         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2071         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2072         board[holdingsStartRow+j*direction][countsColumn]++;
2073     }
2074 }
2075
2076
2077 void
2078 VariantSwitch(Board board, VariantClass newVariant)
2079 {
2080    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2081    static Board oldBoard;
2082
2083    startedFromPositionFile = FALSE;
2084    if(gameInfo.variant == newVariant) return;
2085
2086    /* [HGM] This routine is called each time an assignment is made to
2087     * gameInfo.variant during a game, to make sure the board sizes
2088     * are set to match the new variant. If that means adding or deleting
2089     * holdings, we shift the playing board accordingly
2090     * This kludge is needed because in ICS observe mode, we get boards
2091     * of an ongoing game without knowing the variant, and learn about the
2092     * latter only later. This can be because of the move list we requested,
2093     * in which case the game history is refilled from the beginning anyway,
2094     * but also when receiving holdings of a crazyhouse game. In the latter
2095     * case we want to add those holdings to the already received position.
2096     */
2097
2098
2099    if (appData.debugMode) {
2100      fprintf(debugFP, "Switch board from %s to %s\n",
2101              VariantName(gameInfo.variant), VariantName(newVariant));
2102      setbuf(debugFP, NULL);
2103    }
2104    shuffleOpenings = 0;       /* [HGM] shuffle */
2105    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2106    switch(newVariant)
2107      {
2108      case VariantShogi:
2109        newWidth = 9;  newHeight = 9;
2110        gameInfo.holdingsSize = 7;
2111      case VariantBughouse:
2112      case VariantCrazyhouse:
2113        newHoldingsWidth = 2; break;
2114      case VariantGreat:
2115        newWidth = 10;
2116      case VariantSuper:
2117        newHoldingsWidth = 2;
2118        gameInfo.holdingsSize = 8;
2119        break;
2120      case VariantGothic:
2121      case VariantCapablanca:
2122      case VariantCapaRandom:
2123        newWidth = 10;
2124      default:
2125        newHoldingsWidth = gameInfo.holdingsSize = 0;
2126      };
2127
2128    if(newWidth  != gameInfo.boardWidth  ||
2129       newHeight != gameInfo.boardHeight ||
2130       newHoldingsWidth != gameInfo.holdingsWidth ) {
2131
2132      /* shift position to new playing area, if needed */
2133      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2134        for(i=0; i<BOARD_HEIGHT; i++)
2135          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2136            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2137              board[i][j];
2138        for(i=0; i<newHeight; i++) {
2139          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2140          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2141        }
2142      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2143        for(i=0; i<BOARD_HEIGHT; i++)
2144          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2145            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2146              board[i][j];
2147      }
2148      gameInfo.boardWidth  = newWidth;
2149      gameInfo.boardHeight = newHeight;
2150      gameInfo.holdingsWidth = newHoldingsWidth;
2151      gameInfo.variant = newVariant;
2152      InitDrawingSizes(-2, 0);
2153    } else gameInfo.variant = newVariant;
2154    CopyBoard(oldBoard, board);   // remember correctly formatted board
2155      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2156    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2157 }
2158
2159 static int loggedOn = FALSE;
2160
2161 /*-- Game start info cache: --*/
2162 int gs_gamenum;
2163 char gs_kind[MSG_SIZ];
2164 static char player1Name[128] = "";
2165 static char player2Name[128] = "";
2166 static char cont_seq[] = "\n\\   ";
2167 static int player1Rating = -1;
2168 static int player2Rating = -1;
2169 /*----------------------------*/
2170
2171 ColorClass curColor = ColorNormal;
2172 int suppressKibitz = 0;
2173
2174 // [HGM] seekgraph
2175 Boolean soughtPending = FALSE;
2176 Boolean seekGraphUp;
2177 #define MAX_SEEK_ADS 200
2178 #define SQUARE 0x80
2179 char *seekAdList[MAX_SEEK_ADS];
2180 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2181 float tcList[MAX_SEEK_ADS];
2182 char colorList[MAX_SEEK_ADS];
2183 int nrOfSeekAds = 0;
2184 int minRating = 1010, maxRating = 2800;
2185 int hMargin = 10, vMargin = 20, h, w;
2186 extern int squareSize, lineGap;
2187
2188 void
2189 PlotSeekAd(int i)
2190 {
2191         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2192         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2193         if(r < minRating+100 && r >=0 ) r = minRating+100;
2194         if(r > maxRating) r = maxRating;
2195         if(tc < 1.) tc = 1.;
2196         if(tc > 95.) tc = 95.;
2197         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2198         y = ((double)r - minRating)/(maxRating - minRating)
2199             * (h-vMargin-squareSize/8-1) + vMargin;
2200         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2201         if(strstr(seekAdList[i], " u ")) color = 1;
2202         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2203            !strstr(seekAdList[i], "bullet") &&
2204            !strstr(seekAdList[i], "blitz") &&
2205            !strstr(seekAdList[i], "standard") ) color = 2;
2206         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2207         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2208 }
2209
2210 void
2211 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2212 {
2213         char buf[MSG_SIZ], *ext = "";
2214         VariantClass v = StringToVariant(type);
2215         if(strstr(type, "wild")) {
2216             ext = type + 4; // append wild number
2217             if(v == VariantFischeRandom) type = "chess960"; else
2218             if(v == VariantLoadable) type = "setup"; else
2219             type = VariantName(v);
2220         }
2221         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2222         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2223             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2224             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2225             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2226             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2227             seekNrList[nrOfSeekAds] = nr;
2228             zList[nrOfSeekAds] = 0;
2229             seekAdList[nrOfSeekAds++] = StrSave(buf);
2230             if(plot) PlotSeekAd(nrOfSeekAds-1);
2231         }
2232 }
2233
2234 void
2235 EraseSeekDot(int i)
2236 {
2237     int x = xList[i], y = yList[i], d=squareSize/4, k;
2238     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2239     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2240     // now replot every dot that overlapped
2241     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2242         int xx = xList[k], yy = yList[k];
2243         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2244             DrawSeekDot(xx, yy, colorList[k]);
2245     }
2246 }
2247
2248 void
2249 RemoveSeekAd(int nr)
2250 {
2251         int i;
2252         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2253             EraseSeekDot(i);
2254             if(seekAdList[i]) free(seekAdList[i]);
2255             seekAdList[i] = seekAdList[--nrOfSeekAds];
2256             seekNrList[i] = seekNrList[nrOfSeekAds];
2257             ratingList[i] = ratingList[nrOfSeekAds];
2258             colorList[i]  = colorList[nrOfSeekAds];
2259             tcList[i] = tcList[nrOfSeekAds];
2260             xList[i]  = xList[nrOfSeekAds];
2261             yList[i]  = yList[nrOfSeekAds];
2262             zList[i]  = zList[nrOfSeekAds];
2263             seekAdList[nrOfSeekAds] = NULL;
2264             break;
2265         }
2266 }
2267
2268 Boolean
2269 MatchSoughtLine(char *line)
2270 {
2271     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2272     int nr, base, inc, u=0; char dummy;
2273
2274     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2275        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2276        (u=1) &&
2277        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2278         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2279         // match: compact and save the line
2280         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2281         return TRUE;
2282     }
2283     return FALSE;
2284 }
2285
2286 int
2287 DrawSeekGraph()
2288 {
2289     int i;
2290     if(!seekGraphUp) return FALSE;
2291     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2292     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2293
2294     DrawSeekBackground(0, 0, w, h);
2295     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2296     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2297     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2298         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2299         yy = h-1-yy;
2300         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2301         if(i%500 == 0) {
2302             char buf[MSG_SIZ];
2303             snprintf(buf, MSG_SIZ, "%d", i);
2304             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2305         }
2306     }
2307     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2308     for(i=1; i<100; i+=(i<10?1:5)) {
2309         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2310         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2311         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2312             char buf[MSG_SIZ];
2313             snprintf(buf, MSG_SIZ, "%d", i);
2314             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2315         }
2316     }
2317     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2318     return TRUE;
2319 }
2320
2321 int SeekGraphClick(ClickType click, int x, int y, int moving)
2322 {
2323     static int lastDown = 0, displayed = 0, lastSecond;
2324     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2325         if(click == Release || moving) return FALSE;
2326         nrOfSeekAds = 0;
2327         soughtPending = TRUE;
2328         SendToICS(ics_prefix);
2329         SendToICS("sought\n"); // should this be "sought all"?
2330     } else { // issue challenge based on clicked ad
2331         int dist = 10000; int i, closest = 0, second = 0;
2332         for(i=0; i<nrOfSeekAds; i++) {
2333             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2334             if(d < dist) { dist = d; closest = i; }
2335             second += (d - zList[i] < 120); // count in-range ads
2336             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2337         }
2338         if(dist < 120) {
2339             char buf[MSG_SIZ];
2340             second = (second > 1);
2341             if(displayed != closest || second != lastSecond) {
2342                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2343                 lastSecond = second; displayed = closest;
2344             }
2345             if(click == Press) {
2346                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2347                 lastDown = closest;
2348                 return TRUE;
2349             } // on press 'hit', only show info
2350             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2351             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2352             SendToICS(ics_prefix);
2353             SendToICS(buf);
2354             return TRUE; // let incoming board of started game pop down the graph
2355         } else if(click == Release) { // release 'miss' is ignored
2356             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2357             if(moving == 2) { // right up-click
2358                 nrOfSeekAds = 0; // refresh graph
2359                 soughtPending = TRUE;
2360                 SendToICS(ics_prefix);
2361                 SendToICS("sought\n"); // should this be "sought all"?
2362             }
2363             return TRUE;
2364         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2365         // press miss or release hit 'pop down' seek graph
2366         seekGraphUp = FALSE;
2367         DrawPosition(TRUE, NULL);
2368     }
2369     return TRUE;
2370 }
2371
2372 void
2373 read_from_ics(isr, closure, data, count, error)
2374      InputSourceRef isr;
2375      VOIDSTAR closure;
2376      char *data;
2377      int count;
2378      int error;
2379 {
2380 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2381 #define STARTED_NONE 0
2382 #define STARTED_MOVES 1
2383 #define STARTED_BOARD 2
2384 #define STARTED_OBSERVE 3
2385 #define STARTED_HOLDINGS 4
2386 #define STARTED_CHATTER 5
2387 #define STARTED_COMMENT 6
2388 #define STARTED_MOVES_NOHIDE 7
2389
2390     static int started = STARTED_NONE;
2391     static char parse[20000];
2392     static int parse_pos = 0;
2393     static char buf[BUF_SIZE + 1];
2394     static int firstTime = TRUE, intfSet = FALSE;
2395     static ColorClass prevColor = ColorNormal;
2396     static int savingComment = FALSE;
2397     static int cmatch = 0; // continuation sequence match
2398     char *bp;
2399     char str[MSG_SIZ];
2400     int i, oldi;
2401     int buf_len;
2402     int next_out;
2403     int tkind;
2404     int backup;    /* [DM] For zippy color lines */
2405     char *p;
2406     char talker[MSG_SIZ]; // [HGM] chat
2407     int channel;
2408
2409     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2410
2411     if (appData.debugMode) {
2412       if (!error) {
2413         fprintf(debugFP, "<ICS: ");
2414         show_bytes(debugFP, data, count);
2415         fprintf(debugFP, "\n");
2416       }
2417     }
2418
2419     if (appData.debugMode) { int f = forwardMostMove;
2420         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2421                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2422                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2423     }
2424     if (count > 0) {
2425         /* If last read ended with a partial line that we couldn't parse,
2426            prepend it to the new read and try again. */
2427         if (leftover_len > 0) {
2428             for (i=0; i<leftover_len; i++)
2429               buf[i] = buf[leftover_start + i];
2430         }
2431
2432     /* copy new characters into the buffer */
2433     bp = buf + leftover_len;
2434     buf_len=leftover_len;
2435     for (i=0; i<count; i++)
2436     {
2437         // ignore these
2438         if (data[i] == '\r')
2439             continue;
2440
2441         // join lines split by ICS?
2442         if (!appData.noJoin)
2443         {
2444             /*
2445                 Joining just consists of finding matches against the
2446                 continuation sequence, and discarding that sequence
2447                 if found instead of copying it.  So, until a match
2448                 fails, there's nothing to do since it might be the
2449                 complete sequence, and thus, something we don't want
2450                 copied.
2451             */
2452             if (data[i] == cont_seq[cmatch])
2453             {
2454                 cmatch++;
2455                 if (cmatch == strlen(cont_seq))
2456                 {
2457                     cmatch = 0; // complete match.  just reset the counter
2458
2459                     /*
2460                         it's possible for the ICS to not include the space
2461                         at the end of the last word, making our [correct]
2462                         join operation fuse two separate words.  the server
2463                         does this when the space occurs at the width setting.
2464                     */
2465                     if (!buf_len || buf[buf_len-1] != ' ')
2466                     {
2467                         *bp++ = ' ';
2468                         buf_len++;
2469                     }
2470                 }
2471                 continue;
2472             }
2473             else if (cmatch)
2474             {
2475                 /*
2476                     match failed, so we have to copy what matched before
2477                     falling through and copying this character.  In reality,
2478                     this will only ever be just the newline character, but
2479                     it doesn't hurt to be precise.
2480                 */
2481                 strncpy(bp, cont_seq, cmatch);
2482                 bp += cmatch;
2483                 buf_len += cmatch;
2484                 cmatch = 0;
2485             }
2486         }
2487
2488         // copy this char
2489         *bp++ = data[i];
2490         buf_len++;
2491     }
2492
2493         buf[buf_len] = NULLCHAR;
2494 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2495         next_out = 0;
2496         leftover_start = 0;
2497
2498         i = 0;
2499         while (i < buf_len) {
2500             /* Deal with part of the TELNET option negotiation
2501                protocol.  We refuse to do anything beyond the
2502                defaults, except that we allow the WILL ECHO option,
2503                which ICS uses to turn off password echoing when we are
2504                directly connected to it.  We reject this option
2505                if localLineEditing mode is on (always on in xboard)
2506                and we are talking to port 23, which might be a real
2507                telnet server that will try to keep WILL ECHO on permanently.
2508              */
2509             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2510                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2511                 unsigned char option;
2512                 oldi = i;
2513                 switch ((unsigned char) buf[++i]) {
2514                   case TN_WILL:
2515                     if (appData.debugMode)
2516                       fprintf(debugFP, "\n<WILL ");
2517                     switch (option = (unsigned char) buf[++i]) {
2518                       case TN_ECHO:
2519                         if (appData.debugMode)
2520                           fprintf(debugFP, "ECHO ");
2521                         /* Reply only if this is a change, according
2522                            to the protocol rules. */
2523                         if (remoteEchoOption) break;
2524                         if (appData.localLineEditing &&
2525                             atoi(appData.icsPort) == TN_PORT) {
2526                             TelnetRequest(TN_DONT, TN_ECHO);
2527                         } else {
2528                             EchoOff();
2529                             TelnetRequest(TN_DO, TN_ECHO);
2530                             remoteEchoOption = TRUE;
2531                         }
2532                         break;
2533                       default:
2534                         if (appData.debugMode)
2535                           fprintf(debugFP, "%d ", option);
2536                         /* Whatever this is, we don't want it. */
2537                         TelnetRequest(TN_DONT, option);
2538                         break;
2539                     }
2540                     break;
2541                   case TN_WONT:
2542                     if (appData.debugMode)
2543                       fprintf(debugFP, "\n<WONT ");
2544                     switch (option = (unsigned char) buf[++i]) {
2545                       case TN_ECHO:
2546                         if (appData.debugMode)
2547                           fprintf(debugFP, "ECHO ");
2548                         /* Reply only if this is a change, according
2549                            to the protocol rules. */
2550                         if (!remoteEchoOption) break;
2551                         EchoOn();
2552                         TelnetRequest(TN_DONT, TN_ECHO);
2553                         remoteEchoOption = FALSE;
2554                         break;
2555                       default:
2556                         if (appData.debugMode)
2557                           fprintf(debugFP, "%d ", (unsigned char) option);
2558                         /* Whatever this is, it must already be turned
2559                            off, because we never agree to turn on
2560                            anything non-default, so according to the
2561                            protocol rules, we don't reply. */
2562                         break;
2563                     }
2564                     break;
2565                   case TN_DO:
2566                     if (appData.debugMode)
2567                       fprintf(debugFP, "\n<DO ");
2568                     switch (option = (unsigned char) buf[++i]) {
2569                       default:
2570                         /* Whatever this is, we refuse to do it. */
2571                         if (appData.debugMode)
2572                           fprintf(debugFP, "%d ", option);
2573                         TelnetRequest(TN_WONT, option);
2574                         break;
2575                     }
2576                     break;
2577                   case TN_DONT:
2578                     if (appData.debugMode)
2579                       fprintf(debugFP, "\n<DONT ");
2580                     switch (option = (unsigned char) buf[++i]) {
2581                       default:
2582                         if (appData.debugMode)
2583                           fprintf(debugFP, "%d ", option);
2584                         /* Whatever this is, we are already not doing
2585                            it, because we never agree to do anything
2586                            non-default, so according to the protocol
2587                            rules, we don't reply. */
2588                         break;
2589                     }
2590                     break;
2591                   case TN_IAC:
2592                     if (appData.debugMode)
2593                       fprintf(debugFP, "\n<IAC ");
2594                     /* Doubled IAC; pass it through */
2595                     i--;
2596                     break;
2597                   default:
2598                     if (appData.debugMode)
2599                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2600                     /* Drop all other telnet commands on the floor */
2601                     break;
2602                 }
2603                 if (oldi > next_out)
2604                   SendToPlayer(&buf[next_out], oldi - next_out);
2605                 if (++i > next_out)
2606                   next_out = i;
2607                 continue;
2608             }
2609
2610             /* OK, this at least will *usually* work */
2611             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2612                 loggedOn = TRUE;
2613             }
2614
2615             if (loggedOn && !intfSet) {
2616                 if (ics_type == ICS_ICC) {
2617                   snprintf(str, MSG_SIZ,
2618                           "/set-quietly interface %s\n/set-quietly style 12\n",
2619                           programVersion);
2620                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2621                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2622                 } else if (ics_type == ICS_CHESSNET) {
2623                   snprintf(str, MSG_SIZ, "/style 12\n");
2624                 } else {
2625                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2626                   strcat(str, programVersion);
2627                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2628                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2629                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2630 #ifdef WIN32
2631                   strcat(str, "$iset nohighlight 1\n");
2632 #endif
2633                   strcat(str, "$iset lock 1\n$style 12\n");
2634                 }
2635                 SendToICS(str);
2636                 NotifyFrontendLogin();
2637                 intfSet = TRUE;
2638             }
2639
2640             if (started == STARTED_COMMENT) {
2641                 /* Accumulate characters in comment */
2642                 parse[parse_pos++] = buf[i];
2643                 if (buf[i] == '\n') {
2644                     parse[parse_pos] = NULLCHAR;
2645                     if(chattingPartner>=0) {
2646                         char mess[MSG_SIZ];
2647                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2648                         OutputChatMessage(chattingPartner, mess);
2649                         chattingPartner = -1;
2650                         next_out = i+1; // [HGM] suppress printing in ICS window
2651                     } else
2652                     if(!suppressKibitz) // [HGM] kibitz
2653                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2654                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2655                         int nrDigit = 0, nrAlph = 0, j;
2656                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2657                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2658                         parse[parse_pos] = NULLCHAR;
2659                         // try to be smart: if it does not look like search info, it should go to
2660                         // ICS interaction window after all, not to engine-output window.
2661                         for(j=0; j<parse_pos; j++) { // count letters and digits
2662                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2663                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2664                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2665                         }
2666                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2667                             int depth=0; float score;
2668                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2669                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2670                                 pvInfoList[forwardMostMove-1].depth = depth;
2671                                 pvInfoList[forwardMostMove-1].score = 100*score;
2672                             }
2673                             OutputKibitz(suppressKibitz, parse);
2674                         } else {
2675                             char tmp[MSG_SIZ];
2676                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2677                             SendToPlayer(tmp, strlen(tmp));
2678                         }
2679                         next_out = i+1; // [HGM] suppress printing in ICS window
2680                     }
2681                     started = STARTED_NONE;
2682                 } else {
2683                     /* Don't match patterns against characters in comment */
2684                     i++;
2685                     continue;
2686                 }
2687             }
2688             if (started == STARTED_CHATTER) {
2689                 if (buf[i] != '\n') {
2690                     /* Don't match patterns against characters in chatter */
2691                     i++;
2692                     continue;
2693                 }
2694                 started = STARTED_NONE;
2695                 if(suppressKibitz) next_out = i+1;
2696             }
2697
2698             /* Kludge to deal with rcmd protocol */
2699             if (firstTime && looking_at(buf, &i, "\001*")) {
2700                 DisplayFatalError(&buf[1], 0, 1);
2701                 continue;
2702             } else {
2703                 firstTime = FALSE;
2704             }
2705
2706             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2707                 ics_type = ICS_ICC;
2708                 ics_prefix = "/";
2709                 if (appData.debugMode)
2710                   fprintf(debugFP, "ics_type %d\n", ics_type);
2711                 continue;
2712             }
2713             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2714                 ics_type = ICS_FICS;
2715                 ics_prefix = "$";
2716                 if (appData.debugMode)
2717                   fprintf(debugFP, "ics_type %d\n", ics_type);
2718                 continue;
2719             }
2720             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2721                 ics_type = ICS_CHESSNET;
2722                 ics_prefix = "/";
2723                 if (appData.debugMode)
2724                   fprintf(debugFP, "ics_type %d\n", ics_type);
2725                 continue;
2726             }
2727
2728             if (!loggedOn &&
2729                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2730                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2731                  looking_at(buf, &i, "will be \"*\""))) {
2732               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2733               continue;
2734             }
2735
2736             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2737               char buf[MSG_SIZ];
2738               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2739               DisplayIcsInteractionTitle(buf);
2740               have_set_title = TRUE;
2741             }
2742
2743             /* skip finger notes */
2744             if (started == STARTED_NONE &&
2745                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2746                  (buf[i] == '1' && buf[i+1] == '0')) &&
2747                 buf[i+2] == ':' && buf[i+3] == ' ') {
2748               started = STARTED_CHATTER;
2749               i += 3;
2750               continue;
2751             }
2752
2753             oldi = i;
2754             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2755             if(appData.seekGraph) {
2756                 if(soughtPending && MatchSoughtLine(buf+i)) {
2757                     i = strstr(buf+i, "rated") - buf;
2758                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2759                     next_out = leftover_start = i;
2760                     started = STARTED_CHATTER;
2761                     suppressKibitz = TRUE;
2762                     continue;
2763                 }
2764                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2765                         && looking_at(buf, &i, "* ads displayed")) {
2766                     soughtPending = FALSE;
2767                     seekGraphUp = TRUE;
2768                     DrawSeekGraph();
2769                     continue;
2770                 }
2771                 if(appData.autoRefresh) {
2772                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2773                         int s = (ics_type == ICS_ICC); // ICC format differs
2774                         if(seekGraphUp)
2775                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2776                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2777                         looking_at(buf, &i, "*% "); // eat prompt
2778                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2779                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2780                         next_out = i; // suppress
2781                         continue;
2782                     }
2783                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2784                         char *p = star_match[0];
2785                         while(*p) {
2786                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2787                             while(*p && *p++ != ' '); // next
2788                         }
2789                         looking_at(buf, &i, "*% "); // eat prompt
2790                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2791                         next_out = i;
2792                         continue;
2793                     }
2794                 }
2795             }
2796
2797             /* skip formula vars */
2798             if (started == STARTED_NONE &&
2799                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2800               started = STARTED_CHATTER;
2801               i += 3;
2802               continue;
2803             }
2804
2805             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2806             if (appData.autoKibitz && started == STARTED_NONE &&
2807                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2808                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2809                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2810                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2811                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2812                         suppressKibitz = TRUE;
2813                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2814                         next_out = i;
2815                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2816                                 && (gameMode == IcsPlayingWhite)) ||
2817                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2818                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2819                             started = STARTED_CHATTER; // own kibitz we simply discard
2820                         else {
2821                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2822                             parse_pos = 0; parse[0] = NULLCHAR;
2823                             savingComment = TRUE;
2824                             suppressKibitz = gameMode != IcsObserving ? 2 :
2825                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2826                         }
2827                         continue;
2828                 } else
2829                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2830                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2831                          && atoi(star_match[0])) {
2832                     // suppress the acknowledgements of our own autoKibitz
2833                     char *p;
2834                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2835                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2836                     SendToPlayer(star_match[0], strlen(star_match[0]));
2837                     if(looking_at(buf, &i, "*% ")) // eat prompt
2838                         suppressKibitz = FALSE;
2839                     next_out = i;
2840                     continue;
2841                 }
2842             } // [HGM] kibitz: end of patch
2843
2844             // [HGM] chat: intercept tells by users for which we have an open chat window
2845             channel = -1;
2846             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2847                                            looking_at(buf, &i, "* whispers:") ||
2848                                            looking_at(buf, &i, "* kibitzes:") ||
2849                                            looking_at(buf, &i, "* shouts:") ||
2850                                            looking_at(buf, &i, "* c-shouts:") ||
2851                                            looking_at(buf, &i, "--> * ") ||
2852                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2853                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2854                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2855                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2856                 int p;
2857                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2858                 chattingPartner = -1;
2859
2860                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2861                 for(p=0; p<MAX_CHAT; p++) {
2862                     if(channel == atoi(chatPartner[p])) {
2863                     talker[0] = '['; strcat(talker, "] ");
2864                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2865                     chattingPartner = p; break;
2866                     }
2867                 } else
2868                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2869                 for(p=0; p<MAX_CHAT; p++) {
2870                     if(!strcmp("kibitzes", chatPartner[p])) {
2871                         talker[0] = '['; strcat(talker, "] ");
2872                         chattingPartner = p; break;
2873                     }
2874                 } else
2875                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2876                 for(p=0; p<MAX_CHAT; p++) {
2877                     if(!strcmp("whispers", chatPartner[p])) {
2878                         talker[0] = '['; strcat(talker, "] ");
2879                         chattingPartner = p; break;
2880                     }
2881                 } else
2882                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2883                   if(buf[i-8] == '-' && buf[i-3] == 't')
2884                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2885                     if(!strcmp("c-shouts", chatPartner[p])) {
2886                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2887                         chattingPartner = p; break;
2888                     }
2889                   }
2890                   if(chattingPartner < 0)
2891                   for(p=0; p<MAX_CHAT; p++) {
2892                     if(!strcmp("shouts", chatPartner[p])) {
2893                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2894                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2895                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2896                         chattingPartner = p; break;
2897                     }
2898                   }
2899                 }
2900                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2901                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2902                     talker[0] = 0; Colorize(ColorTell, FALSE);
2903                     chattingPartner = p; break;
2904                 }
2905                 if(chattingPartner<0) i = oldi; else {
2906                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2907                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2908                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2909                     started = STARTED_COMMENT;
2910                     parse_pos = 0; parse[0] = NULLCHAR;
2911                     savingComment = 3 + chattingPartner; // counts as TRUE
2912                     suppressKibitz = TRUE;
2913                     continue;
2914                 }
2915             } // [HGM] chat: end of patch
2916
2917             if (appData.zippyTalk || appData.zippyPlay) {
2918                 /* [DM] Backup address for color zippy lines */
2919                 backup = i;
2920 #if ZIPPY
2921                if (loggedOn == TRUE)
2922                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2923                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
2924 #endif
2925             } // [DM] 'else { ' deleted
2926                 if (
2927                     /* Regular tells and says */
2928                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2929                     looking_at(buf, &i, "* (your partner) tells you: ") ||
2930                     looking_at(buf, &i, "* says: ") ||
2931                     /* Don't color "message" or "messages" output */
2932                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
2933                     looking_at(buf, &i, "*. * at *:*: ") ||
2934                     looking_at(buf, &i, "--* (*:*): ") ||
2935                     /* Message notifications (same color as tells) */
2936                     looking_at(buf, &i, "* has left a message ") ||
2937                     looking_at(buf, &i, "* just sent you a message:\n") ||
2938                     /* Whispers and kibitzes */
2939                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
2940                     looking_at(buf, &i, "* kibitzes: ") ||
2941                     /* Channel tells */
2942                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
2943
2944                   if (tkind == 1 && strchr(star_match[0], ':')) {
2945                       /* Avoid "tells you:" spoofs in channels */
2946                      tkind = 3;
2947                   }
2948                   if (star_match[0][0] == NULLCHAR ||
2949                       strchr(star_match[0], ' ') ||
2950                       (tkind == 3 && strchr(star_match[1], ' '))) {
2951                     /* Reject bogus matches */
2952                     i = oldi;
2953                   } else {
2954                     if (appData.colorize) {
2955                       if (oldi > next_out) {
2956                         SendToPlayer(&buf[next_out], oldi - next_out);
2957                         next_out = oldi;
2958                       }
2959                       switch (tkind) {
2960                       case 1:
2961                         Colorize(ColorTell, FALSE);
2962                         curColor = ColorTell;
2963                         break;
2964                       case 2:
2965                         Colorize(ColorKibitz, FALSE);
2966                         curColor = ColorKibitz;
2967                         break;
2968                       case 3:
2969                         p = strrchr(star_match[1], '(');
2970                         if (p == NULL) {
2971                           p = star_match[1];
2972                         } else {
2973                           p++;
2974                         }
2975                         if (atoi(p) == 1) {
2976                           Colorize(ColorChannel1, FALSE);
2977                           curColor = ColorChannel1;
2978                         } else {
2979                           Colorize(ColorChannel, FALSE);
2980                           curColor = ColorChannel;
2981                         }
2982                         break;
2983                       case 5:
2984                         curColor = ColorNormal;
2985                         break;
2986                       }
2987                     }
2988                     if (started == STARTED_NONE && appData.autoComment &&
2989                         (gameMode == IcsObserving ||
2990                          gameMode == IcsPlayingWhite ||
2991                          gameMode == IcsPlayingBlack)) {
2992                       parse_pos = i - oldi;
2993                       memcpy(parse, &buf[oldi], parse_pos);
2994                       parse[parse_pos] = NULLCHAR;
2995                       started = STARTED_COMMENT;
2996                       savingComment = TRUE;
2997                     } else {
2998                       started = STARTED_CHATTER;
2999                       savingComment = FALSE;
3000                     }
3001                     loggedOn = TRUE;
3002                     continue;
3003                   }
3004                 }
3005
3006                 if (looking_at(buf, &i, "* s-shouts: ") ||
3007                     looking_at(buf, &i, "* c-shouts: ")) {
3008                     if (appData.colorize) {
3009                         if (oldi > next_out) {
3010                             SendToPlayer(&buf[next_out], oldi - next_out);
3011                             next_out = oldi;
3012                         }
3013                         Colorize(ColorSShout, FALSE);
3014                         curColor = ColorSShout;
3015                     }
3016                     loggedOn = TRUE;
3017                     started = STARTED_CHATTER;
3018                     continue;
3019                 }
3020
3021                 if (looking_at(buf, &i, "--->")) {
3022                     loggedOn = TRUE;
3023                     continue;
3024                 }
3025
3026                 if (looking_at(buf, &i, "* shouts: ") ||
3027                     looking_at(buf, &i, "--> ")) {
3028                     if (appData.colorize) {
3029                         if (oldi > next_out) {
3030                             SendToPlayer(&buf[next_out], oldi - next_out);
3031                             next_out = oldi;
3032                         }
3033                         Colorize(ColorShout, FALSE);
3034                         curColor = ColorShout;
3035                     }
3036                     loggedOn = TRUE;
3037                     started = STARTED_CHATTER;
3038                     continue;
3039                 }
3040
3041                 if (looking_at( buf, &i, "Challenge:")) {
3042                     if (appData.colorize) {
3043                         if (oldi > next_out) {
3044                             SendToPlayer(&buf[next_out], oldi - next_out);
3045                             next_out = oldi;
3046                         }
3047                         Colorize(ColorChallenge, FALSE);
3048                         curColor = ColorChallenge;
3049                     }
3050                     loggedOn = TRUE;
3051                     continue;
3052                 }
3053
3054                 if (looking_at(buf, &i, "* offers you") ||
3055                     looking_at(buf, &i, "* offers to be") ||
3056                     looking_at(buf, &i, "* would like to") ||
3057                     looking_at(buf, &i, "* requests to") ||
3058                     looking_at(buf, &i, "Your opponent offers") ||
3059                     looking_at(buf, &i, "Your opponent requests")) {
3060
3061                     if (appData.colorize) {
3062                         if (oldi > next_out) {
3063                             SendToPlayer(&buf[next_out], oldi - next_out);
3064                             next_out = oldi;
3065                         }
3066                         Colorize(ColorRequest, FALSE);
3067                         curColor = ColorRequest;
3068                     }
3069                     continue;
3070                 }
3071
3072                 if (looking_at(buf, &i, "* (*) seeking")) {
3073                     if (appData.colorize) {
3074                         if (oldi > next_out) {
3075                             SendToPlayer(&buf[next_out], oldi - next_out);
3076                             next_out = oldi;
3077                         }
3078                         Colorize(ColorSeek, FALSE);
3079                         curColor = ColorSeek;
3080                     }
3081                     continue;
3082             }
3083
3084             if (looking_at(buf, &i, "\\   ")) {
3085                 if (prevColor != ColorNormal) {
3086                     if (oldi > next_out) {
3087                         SendToPlayer(&buf[next_out], oldi - next_out);
3088                         next_out = oldi;
3089                     }
3090                     Colorize(prevColor, TRUE);
3091                     curColor = prevColor;
3092                 }
3093                 if (savingComment) {
3094                     parse_pos = i - oldi;
3095                     memcpy(parse, &buf[oldi], parse_pos);
3096                     parse[parse_pos] = NULLCHAR;
3097                     started = STARTED_COMMENT;
3098                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3099                         chattingPartner = savingComment - 3; // kludge to remember the box
3100                 } else {
3101                     started = STARTED_CHATTER;
3102                 }
3103                 continue;
3104             }
3105
3106             if (looking_at(buf, &i, "Black Strength :") ||
3107                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3108                 looking_at(buf, &i, "<10>") ||
3109                 looking_at(buf, &i, "#@#")) {
3110                 /* Wrong board style */
3111                 loggedOn = TRUE;
3112                 SendToICS(ics_prefix);
3113                 SendToICS("set style 12\n");
3114                 SendToICS(ics_prefix);
3115                 SendToICS("refresh\n");
3116                 continue;
3117             }
3118
3119             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3120                 ICSInitScript();
3121                 have_sent_ICS_logon = 1;
3122                 /* if we don't send the login/password via icsLogon, use special readline
3123                    code for it */
3124                 if (strlen(appData.icsLogon)==0)
3125                   {
3126                     sending_ICS_password = 0; // in case we come back to login
3127                     sending_ICS_login = 1;
3128                   };
3129                 continue;
3130             }
3131             /* need to shadow the password */
3132             if (!sending_ICS_password && looking_at(buf, &i, "password:")) {
3133               /* if we don't send the login/password via icsLogon, use special readline
3134                  code for it */
3135               if (strlen(appData.icsLogon)==0)
3136                 sending_ICS_password = 1;
3137               continue;
3138             }
3139
3140             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3141                 (looking_at(buf, &i, "\n<12> ") ||
3142                  looking_at(buf, &i, "<12> "))) {
3143                 loggedOn = TRUE;
3144                 if (oldi > next_out) {
3145                     SendToPlayer(&buf[next_out], oldi - next_out);
3146                 }
3147                 next_out = i;
3148                 started = STARTED_BOARD;
3149                 parse_pos = 0;
3150                 continue;
3151             }
3152
3153             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3154                 looking_at(buf, &i, "<b1> ")) {
3155                 if (oldi > next_out) {
3156                     SendToPlayer(&buf[next_out], oldi - next_out);
3157                 }
3158                 next_out = i;
3159                 started = STARTED_HOLDINGS;
3160                 parse_pos = 0;
3161                 continue;
3162             }
3163
3164             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3165                 loggedOn = TRUE;
3166                 /* Header for a move list -- first line */
3167
3168                 switch (ics_getting_history) {
3169                   case H_FALSE:
3170                     switch (gameMode) {
3171                       case IcsIdle:
3172                       case BeginningOfGame:
3173                         /* User typed "moves" or "oldmoves" while we
3174                            were idle.  Pretend we asked for these
3175                            moves and soak them up so user can step
3176                            through them and/or save them.
3177                            */
3178                         Reset(FALSE, TRUE);
3179                         gameMode = IcsObserving;
3180                         ModeHighlight();
3181                         ics_gamenum = -1;
3182                         ics_getting_history = H_GOT_UNREQ_HEADER;
3183                         break;
3184                       case EditGame: /*?*/
3185                       case EditPosition: /*?*/
3186                         /* Should above feature work in these modes too? */
3187                         /* For now it doesn't */
3188                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3189                         break;
3190                       default:
3191                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3192                         break;
3193                     }
3194                     break;
3195                   case H_REQUESTED:
3196                     /* Is this the right one? */
3197                     if (gameInfo.white && gameInfo.black &&
3198                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3199                         strcmp(gameInfo.black, star_match[2]) == 0) {
3200                         /* All is well */
3201                         ics_getting_history = H_GOT_REQ_HEADER;
3202                     }
3203                     break;
3204                   case H_GOT_REQ_HEADER:
3205                   case H_GOT_UNREQ_HEADER:
3206                   case H_GOT_UNWANTED_HEADER:
3207                   case H_GETTING_MOVES:
3208                     /* Should not happen */
3209                     DisplayError(_("Error gathering move list: two headers"), 0);
3210                     ics_getting_history = H_FALSE;
3211                     break;
3212                 }
3213
3214                 /* Save player ratings into gameInfo if needed */
3215                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3216                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3217                     (gameInfo.whiteRating == -1 ||
3218                      gameInfo.blackRating == -1)) {
3219
3220                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3221                     gameInfo.blackRating = string_to_rating(star_match[3]);
3222                     if (appData.debugMode)
3223                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3224                               gameInfo.whiteRating, gameInfo.blackRating);
3225                 }
3226                 continue;
3227             }
3228
3229             if (looking_at(buf, &i,
3230               "* * match, initial time: * minute*, increment: * second")) {
3231                 /* Header for a move list -- second line */
3232                 /* Initial board will follow if this is a wild game */
3233                 if (gameInfo.event != NULL) free(gameInfo.event);
3234                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3235                 gameInfo.event = StrSave(str);
3236                 /* [HGM] we switched variant. Translate boards if needed. */
3237                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3238                 continue;
3239             }
3240
3241             if (looking_at(buf, &i, "Move  ")) {
3242                 /* Beginning of a move list */
3243                 switch (ics_getting_history) {
3244                   case H_FALSE:
3245                     /* Normally should not happen */
3246                     /* Maybe user hit reset while we were parsing */
3247                     break;
3248                   case H_REQUESTED:
3249                     /* Happens if we are ignoring a move list that is not
3250                      * the one we just requested.  Common if the user
3251                      * tries to observe two games without turning off
3252                      * getMoveList */
3253                     break;
3254                   case H_GETTING_MOVES:
3255                     /* Should not happen */
3256                     DisplayError(_("Error gathering move list: nested"), 0);
3257                     ics_getting_history = H_FALSE;
3258                     break;
3259                   case H_GOT_REQ_HEADER:
3260                     ics_getting_history = H_GETTING_MOVES;
3261                     started = STARTED_MOVES;
3262                     parse_pos = 0;
3263                     if (oldi > next_out) {
3264                         SendToPlayer(&buf[next_out], oldi - next_out);
3265                     }
3266                     break;
3267                   case H_GOT_UNREQ_HEADER:
3268                     ics_getting_history = H_GETTING_MOVES;
3269                     started = STARTED_MOVES_NOHIDE;
3270                     parse_pos = 0;
3271                     break;
3272                   case H_GOT_UNWANTED_HEADER:
3273                     ics_getting_history = H_FALSE;
3274                     break;
3275                 }
3276                 continue;
3277             }
3278
3279             if (looking_at(buf, &i, "% ") ||
3280                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3281                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3282                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3283                     soughtPending = FALSE;
3284                     seekGraphUp = TRUE;
3285                     DrawSeekGraph();
3286                 }
3287                 if(suppressKibitz) next_out = i;
3288                 savingComment = FALSE;
3289                 suppressKibitz = 0;
3290                 switch (started) {
3291                   case STARTED_MOVES:
3292                   case STARTED_MOVES_NOHIDE:
3293                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3294                     parse[parse_pos + i - oldi] = NULLCHAR;
3295                     ParseGameHistory(parse);
3296 #if ZIPPY
3297                     if (appData.zippyPlay && first.initDone) {
3298                         FeedMovesToProgram(&first, forwardMostMove);
3299                         if (gameMode == IcsPlayingWhite) {
3300                             if (WhiteOnMove(forwardMostMove)) {
3301                                 if (first.sendTime) {
3302                                   if (first.useColors) {
3303                                     SendToProgram("black\n", &first);
3304                                   }
3305                                   SendTimeRemaining(&first, TRUE);
3306                                 }
3307                                 if (first.useColors) {
3308                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3309                                 }
3310                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3311                                 first.maybeThinking = TRUE;
3312                             } else {
3313                                 if (first.usePlayother) {
3314                                   if (first.sendTime) {
3315                                     SendTimeRemaining(&first, TRUE);
3316                                   }
3317                                   SendToProgram("playother\n", &first);
3318                                   firstMove = FALSE;
3319                                 } else {
3320                                   firstMove = TRUE;
3321                                 }
3322                             }
3323                         } else if (gameMode == IcsPlayingBlack) {
3324                             if (!WhiteOnMove(forwardMostMove)) {
3325                                 if (first.sendTime) {
3326                                   if (first.useColors) {
3327                                     SendToProgram("white\n", &first);
3328                                   }
3329                                   SendTimeRemaining(&first, FALSE);
3330                                 }
3331                                 if (first.useColors) {
3332                                   SendToProgram("black\n", &first);
3333                                 }
3334                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3335                                 first.maybeThinking = TRUE;
3336                             } else {
3337                                 if (first.usePlayother) {
3338                                   if (first.sendTime) {
3339                                     SendTimeRemaining(&first, FALSE);
3340                                   }
3341                                   SendToProgram("playother\n", &first);
3342                                   firstMove = FALSE;
3343                                 } else {
3344                                   firstMove = TRUE;
3345                                 }
3346                             }
3347                         }
3348                     }
3349 #endif
3350                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3351                         /* Moves came from oldmoves or moves command
3352                            while we weren't doing anything else.
3353                            */
3354                         currentMove = forwardMostMove;
3355                         ClearHighlights();/*!!could figure this out*/
3356                         flipView = appData.flipView;
3357                         DrawPosition(TRUE, boards[currentMove]);
3358                         DisplayBothClocks();
3359                         snprintf(str, MSG_SIZ, "%s vs. %s",
3360                                 gameInfo.white, gameInfo.black);
3361                         DisplayTitle(str);
3362                         gameMode = IcsIdle;
3363                     } else {
3364                         /* Moves were history of an active game */
3365                         if (gameInfo.resultDetails != NULL) {
3366                             free(gameInfo.resultDetails);
3367                             gameInfo.resultDetails = NULL;
3368                         }
3369                     }
3370                     HistorySet(parseList, backwardMostMove,
3371                                forwardMostMove, currentMove-1);
3372                     DisplayMove(currentMove - 1);
3373                     if (started == STARTED_MOVES) next_out = i;
3374                     started = STARTED_NONE;
3375                     ics_getting_history = H_FALSE;
3376                     break;
3377
3378                   case STARTED_OBSERVE:
3379                     started = STARTED_NONE;
3380                     SendToICS(ics_prefix);
3381                     SendToICS("refresh\n");
3382                     break;
3383
3384                   default:
3385                     break;
3386                 }
3387                 if(bookHit) { // [HGM] book: simulate book reply
3388                     static char bookMove[MSG_SIZ]; // a bit generous?
3389
3390                     programStats.nodes = programStats.depth = programStats.time =
3391                     programStats.score = programStats.got_only_move = 0;
3392                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3393
3394                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3395                     strcat(bookMove, bookHit);
3396                     HandleMachineMove(bookMove, &first);
3397                 }
3398                 continue;
3399             }
3400
3401             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3402                  started == STARTED_HOLDINGS ||
3403                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3404                 /* Accumulate characters in move list or board */
3405                 parse[parse_pos++] = buf[i];
3406             }
3407
3408             /* Start of game messages.  Mostly we detect start of game
3409                when the first board image arrives.  On some versions
3410                of the ICS, though, we need to do a "refresh" after starting
3411                to observe in order to get the current board right away. */
3412             if (looking_at(buf, &i, "Adding game * to observation list")) {
3413                 started = STARTED_OBSERVE;
3414                 continue;
3415             }
3416
3417             /* Handle auto-observe */
3418             if (appData.autoObserve &&
3419                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3420                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3421                 char *player;
3422                 /* Choose the player that was highlighted, if any. */
3423                 if (star_match[0][0] == '\033' ||
3424                     star_match[1][0] != '\033') {
3425                     player = star_match[0];
3426                 } else {
3427                     player = star_match[2];
3428                 }
3429                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3430                         ics_prefix, StripHighlightAndTitle(player));
3431                 SendToICS(str);
3432
3433                 /* Save ratings from notify string */
3434                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3435                 player1Rating = string_to_rating(star_match[1]);
3436                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3437                 player2Rating = string_to_rating(star_match[3]);
3438
3439                 if (appData.debugMode)
3440                   fprintf(debugFP,
3441                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3442                           player1Name, player1Rating,
3443                           player2Name, player2Rating);
3444
3445                 continue;
3446             }
3447
3448             /* Deal with automatic examine mode after a game,
3449                and with IcsObserving -> IcsExamining transition */
3450             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3451                 looking_at(buf, &i, "has made you an examiner of game *")) {
3452
3453                 int gamenum = atoi(star_match[0]);
3454                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3455                     gamenum == ics_gamenum) {
3456                     /* We were already playing or observing this game;
3457                        no need to refetch history */
3458                     gameMode = IcsExamining;
3459                     if (pausing) {
3460                         pauseExamForwardMostMove = forwardMostMove;
3461                     } else if (currentMove < forwardMostMove) {
3462                         ForwardInner(forwardMostMove);
3463                     }
3464                 } else {
3465                     /* I don't think this case really can happen */
3466                     SendToICS(ics_prefix);
3467                     SendToICS("refresh\n");
3468                 }
3469                 continue;
3470             }
3471
3472             /* Error messages */
3473 //          if (ics_user_moved) {
3474             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3475                 if (looking_at(buf, &i, "Illegal move") ||
3476                     looking_at(buf, &i, "Not a legal move") ||
3477                     looking_at(buf, &i, "Your king is in check") ||
3478                     looking_at(buf, &i, "It isn't your turn") ||
3479                     looking_at(buf, &i, "It is not your move")) {
3480                     /* Illegal move */
3481                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3482                         currentMove = forwardMostMove-1;
3483                         DisplayMove(currentMove - 1); /* before DMError */
3484                         DrawPosition(FALSE, boards[currentMove]);
3485                         SwitchClocks(forwardMostMove-1); // [HGM] race
3486                         DisplayBothClocks();
3487                     }
3488                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3489                     ics_user_moved = 0;
3490                     continue;
3491                 }
3492             }
3493
3494             if (looking_at(buf, &i, "still have time") ||
3495                 looking_at(buf, &i, "not out of time") ||
3496                 looking_at(buf, &i, "either player is out of time") ||
3497                 looking_at(buf, &i, "has timeseal; checking")) {
3498                 /* We must have called his flag a little too soon */
3499                 whiteFlag = blackFlag = FALSE;
3500                 continue;
3501             }
3502
3503             if (looking_at(buf, &i, "added * seconds to") ||
3504                 looking_at(buf, &i, "seconds were added to")) {
3505                 /* Update the clocks */
3506                 SendToICS(ics_prefix);
3507                 SendToICS("refresh\n");
3508                 continue;
3509             }
3510
3511             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3512                 ics_clock_paused = TRUE;
3513                 StopClocks();
3514                 continue;
3515             }
3516
3517             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3518                 ics_clock_paused = FALSE;
3519                 StartClocks();
3520                 continue;
3521             }
3522
3523             /* Grab player ratings from the Creating: message.
3524                Note we have to check for the special case when
3525                the ICS inserts things like [white] or [black]. */
3526             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3527                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3528                 /* star_matches:
3529                    0    player 1 name (not necessarily white)
3530                    1    player 1 rating
3531                    2    empty, white, or black (IGNORED)
3532                    3    player 2 name (not necessarily black)
3533                    4    player 2 rating
3534
3535                    The names/ratings are sorted out when the game
3536                    actually starts (below).
3537                 */
3538                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3539                 player1Rating = string_to_rating(star_match[1]);
3540                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3541                 player2Rating = string_to_rating(star_match[4]);
3542
3543                 if (appData.debugMode)
3544                   fprintf(debugFP,
3545                           "Ratings from 'Creating:' %s %d, %s %d\n",
3546                           player1Name, player1Rating,
3547                           player2Name, player2Rating);
3548
3549                 continue;
3550             }
3551
3552             /* Improved generic start/end-of-game messages */
3553             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3554                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3555                 /* If tkind == 0: */
3556                 /* star_match[0] is the game number */
3557                 /*           [1] is the white player's name */
3558                 /*           [2] is the black player's name */
3559                 /* For end-of-game: */
3560                 /*           [3] is the reason for the game end */
3561                 /*           [4] is a PGN end game-token, preceded by " " */
3562                 /* For start-of-game: */
3563                 /*           [3] begins with "Creating" or "Continuing" */
3564                 /*           [4] is " *" or empty (don't care). */
3565                 int gamenum = atoi(star_match[0]);
3566                 char *whitename, *blackname, *why, *endtoken;
3567                 ChessMove endtype = EndOfFile;
3568
3569                 if (tkind == 0) {
3570                   whitename = star_match[1];
3571                   blackname = star_match[2];
3572                   why = star_match[3];
3573                   endtoken = star_match[4];
3574                 } else {
3575                   whitename = star_match[1];
3576                   blackname = star_match[3];
3577                   why = star_match[5];
3578                   endtoken = star_match[6];
3579                 }
3580
3581                 /* Game start messages */
3582                 if (strncmp(why, "Creating ", 9) == 0 ||
3583                     strncmp(why, "Continuing ", 11) == 0) {
3584                     gs_gamenum = gamenum;
3585                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3586                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3587 #if ZIPPY
3588                     if (appData.zippyPlay) {
3589                         ZippyGameStart(whitename, blackname);
3590                     }
3591 #endif /*ZIPPY*/
3592                     partnerBoardValid = FALSE; // [HGM] bughouse
3593                     continue;
3594                 }
3595
3596                 /* Game end messages */
3597                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3598                     ics_gamenum != gamenum) {
3599                     continue;
3600                 }
3601                 while (endtoken[0] == ' ') endtoken++;
3602                 switch (endtoken[0]) {
3603                   case '*':
3604                   default:
3605                     endtype = GameUnfinished;
3606                     break;
3607                   case '0':
3608                     endtype = BlackWins;
3609                     break;
3610                   case '1':
3611                     if (endtoken[1] == '/')
3612                       endtype = GameIsDrawn;
3613                     else
3614                       endtype = WhiteWins;
3615                     break;
3616                 }
3617                 GameEnds(endtype, why, GE_ICS);
3618 #if ZIPPY
3619                 if (appData.zippyPlay && first.initDone) {
3620                     ZippyGameEnd(endtype, why);
3621                     if (first.pr == NULL) {
3622                       /* Start the next process early so that we'll
3623                          be ready for the next challenge */
3624                       StartChessProgram(&first);
3625                     }
3626                     /* Send "new" early, in case this command takes
3627                        a long time to finish, so that we'll be ready
3628                        for the next challenge. */
3629                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3630                     Reset(TRUE, TRUE);
3631                 }
3632 #endif /*ZIPPY*/
3633                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3634                 continue;
3635             }
3636
3637             if (looking_at(buf, &i, "Removing game * from observation") ||
3638                 looking_at(buf, &i, "no longer observing game *") ||
3639                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3640                 if (gameMode == IcsObserving &&
3641                     atoi(star_match[0]) == ics_gamenum)
3642                   {
3643                       /* icsEngineAnalyze */
3644                       if (appData.icsEngineAnalyze) {
3645                             ExitAnalyzeMode();
3646                             ModeHighlight();
3647                       }
3648                       StopClocks();
3649                       gameMode = IcsIdle;
3650                       ics_gamenum = -1;
3651                       ics_user_moved = FALSE;
3652                   }
3653                 continue;
3654             }
3655
3656             if (looking_at(buf, &i, "no longer examining game *")) {
3657                 if (gameMode == IcsExamining &&
3658                     atoi(star_match[0]) == ics_gamenum)
3659                   {
3660                       gameMode = IcsIdle;
3661                       ics_gamenum = -1;
3662                       ics_user_moved = FALSE;
3663                   }
3664                 continue;
3665             }
3666
3667             /* Advance leftover_start past any newlines we find,
3668                so only partial lines can get reparsed */
3669             if (looking_at(buf, &i, "\n")) {
3670                 prevColor = curColor;
3671                 if (curColor != ColorNormal) {
3672                     if (oldi > next_out) {
3673                         SendToPlayer(&buf[next_out], oldi - next_out);
3674                         next_out = oldi;
3675                     }
3676                     Colorize(ColorNormal, FALSE);
3677                     curColor = ColorNormal;
3678                 }
3679                 if (started == STARTED_BOARD) {
3680                     started = STARTED_NONE;
3681                     parse[parse_pos] = NULLCHAR;
3682                     ParseBoard12(parse);
3683                     ics_user_moved = 0;
3684
3685                     /* Send premove here */
3686                     if (appData.premove) {
3687                       char str[MSG_SIZ];
3688                       if (currentMove == 0 &&
3689                           gameMode == IcsPlayingWhite &&
3690                           appData.premoveWhite) {
3691                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3692                         if (appData.debugMode)
3693                           fprintf(debugFP, "Sending premove:\n");
3694                         SendToICS(str);
3695                       } else if (currentMove == 1 &&
3696                                  gameMode == IcsPlayingBlack &&
3697                                  appData.premoveBlack) {
3698                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3699                         if (appData.debugMode)
3700                           fprintf(debugFP, "Sending premove:\n");
3701                         SendToICS(str);
3702                       } else if (gotPremove) {
3703                         gotPremove = 0;
3704                         ClearPremoveHighlights();
3705                         if (appData.debugMode)
3706                           fprintf(debugFP, "Sending premove:\n");
3707                           UserMoveEvent(premoveFromX, premoveFromY,
3708                                         premoveToX, premoveToY,
3709                                         premovePromoChar);
3710                       }
3711                     }
3712
3713                     /* Usually suppress following prompt */
3714                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3715                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3716                         if (looking_at(buf, &i, "*% ")) {
3717                             savingComment = FALSE;
3718                             suppressKibitz = 0;
3719                         }
3720                     }
3721                     next_out = i;
3722                 } else if (started == STARTED_HOLDINGS) {
3723                     int gamenum;
3724                     char new_piece[MSG_SIZ];
3725                     started = STARTED_NONE;
3726                     parse[parse_pos] = NULLCHAR;
3727                     if (appData.debugMode)
3728                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3729                                                         parse, currentMove);
3730                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3731                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3732                         if (gameInfo.variant == VariantNormal) {
3733                           /* [HGM] We seem to switch variant during a game!
3734                            * Presumably no holdings were displayed, so we have
3735                            * to move the position two files to the right to
3736                            * create room for them!
3737                            */
3738                           VariantClass newVariant;
3739                           switch(gameInfo.boardWidth) { // base guess on board width
3740                                 case 9:  newVariant = VariantShogi; break;
3741                                 case 10: newVariant = VariantGreat; break;
3742                                 default: newVariant = VariantCrazyhouse; break;
3743                           }
3744                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3745                           /* Get a move list just to see the header, which
3746                              will tell us whether this is really bug or zh */
3747                           if (ics_getting_history == H_FALSE) {
3748                             ics_getting_history = H_REQUESTED;
3749                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3750                             SendToICS(str);
3751                           }
3752                         }
3753                         new_piece[0] = NULLCHAR;
3754                         sscanf(parse, "game %d white [%s black [%s <- %s",
3755                                &gamenum, white_holding, black_holding,
3756                                new_piece);
3757                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3758                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3759                         /* [HGM] copy holdings to board holdings area */
3760                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3761                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3762                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3763 #if ZIPPY
3764                         if (appData.zippyPlay && first.initDone) {
3765                             ZippyHoldings(white_holding, black_holding,
3766                                           new_piece);
3767                         }
3768 #endif /*ZIPPY*/
3769                         if (tinyLayout || smallLayout) {
3770                             char wh[16], bh[16];
3771                             PackHolding(wh, white_holding);
3772                             PackHolding(bh, black_holding);
3773                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3774                                     gameInfo.white, gameInfo.black);
3775                         } else {
3776                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3777                                     gameInfo.white, white_holding,
3778                                     gameInfo.black, black_holding);
3779                         }
3780                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3781                         DrawPosition(FALSE, boards[currentMove]);
3782                         DisplayTitle(str);
3783                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3784                         sscanf(parse, "game %d white [%s black [%s <- %s",
3785                                &gamenum, white_holding, black_holding,
3786                                new_piece);
3787                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3788                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3789                         /* [HGM] copy holdings to partner-board holdings area */
3790                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3791                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3792                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3793                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3794                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3795                       }
3796                     }
3797                     /* Suppress following prompt */
3798                     if (looking_at(buf, &i, "*% ")) {
3799                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3800                         savingComment = FALSE;
3801                         suppressKibitz = 0;
3802                     }
3803                     next_out = i;
3804                 }
3805                 continue;
3806             }
3807
3808             i++;                /* skip unparsed character and loop back */
3809         }
3810
3811         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3812 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3813 //          SendToPlayer(&buf[next_out], i - next_out);
3814             started != STARTED_HOLDINGS && leftover_start > next_out) {
3815             SendToPlayer(&buf[next_out], leftover_start - next_out);
3816             next_out = i;
3817         }
3818
3819         leftover_len = buf_len - leftover_start;
3820         /* if buffer ends with something we couldn't parse,
3821            reparse it after appending the next read */
3822
3823     } else if (count == 0) {
3824         RemoveInputSource(isr);
3825         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3826     } else {
3827         DisplayFatalError(_("Error reading from ICS"), error, 1);
3828     }
3829 }
3830
3831
3832 /* Board style 12 looks like this:
3833
3834    <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
3835
3836  * The "<12> " is stripped before it gets to this routine.  The two
3837  * trailing 0's (flip state and clock ticking) are later addition, and
3838  * some chess servers may not have them, or may have only the first.
3839  * Additional trailing fields may be added in the future.
3840  */
3841
3842 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
3843
3844 #define RELATION_OBSERVING_PLAYED    0
3845 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3846 #define RELATION_PLAYING_MYMOVE      1
3847 #define RELATION_PLAYING_NOTMYMOVE  -1
3848 #define RELATION_EXAMINING           2
3849 #define RELATION_ISOLATED_BOARD     -3
3850 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3851
3852 void
3853 ParseBoard12(string)
3854      char *string;
3855 {
3856     GameMode newGameMode;
3857     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3858     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3859     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3860     char to_play, board_chars[200];
3861     char move_str[500], str[500], elapsed_time[500];
3862     char black[32], white[32];
3863     Board board;
3864     int prevMove = currentMove;
3865     int ticking = 2;
3866     ChessMove moveType;
3867     int fromX, fromY, toX, toY;
3868     char promoChar;
3869     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3870     char *bookHit = NULL; // [HGM] book
3871     Boolean weird = FALSE, reqFlag = FALSE;
3872
3873     fromX = fromY = toX = toY = -1;
3874
3875     newGame = FALSE;
3876
3877     if (appData.debugMode)
3878       fprintf(debugFP, _("Parsing board: %s\n"), string);
3879
3880     move_str[0] = NULLCHAR;
3881     elapsed_time[0] = NULLCHAR;
3882     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3883         int  i = 0, j;
3884         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3885             if(string[i] == ' ') { ranks++; files = 0; }
3886             else files++;
3887             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3888             i++;
3889         }
3890         for(j = 0; j <i; j++) board_chars[j] = string[j];
3891         board_chars[i] = '\0';
3892         string += i + 1;
3893     }
3894     n = sscanf(string, PATTERN, &to_play, &double_push,
3895                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3896                &gamenum, white, black, &relation, &basetime, &increment,
3897                &white_stren, &black_stren, &white_time, &black_time,
3898                &moveNum, str, elapsed_time, move_str, &ics_flip,
3899                &ticking);
3900
3901     if (n < 21) {
3902         snprintf(str, sizeof(str), _("Failed to parse board string:\n\"%s\""), string);
3903         DisplayError(str, 0);
3904         return;
3905     }
3906
3907     /* Convert the move number to internal form */
3908     moveNum = (moveNum - 1) * 2;
3909     if (to_play == 'B') moveNum++;
3910     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3911       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3912                         0, 1);
3913       return;
3914     }
3915
3916     switch (relation) {
3917       case RELATION_OBSERVING_PLAYED:
3918       case RELATION_OBSERVING_STATIC:
3919         if (gamenum == -1) {
3920             /* Old ICC buglet */
3921             relation = RELATION_OBSERVING_STATIC;
3922         }
3923         newGameMode = IcsObserving;
3924         break;
3925       case RELATION_PLAYING_MYMOVE:
3926       case RELATION_PLAYING_NOTMYMOVE:
3927         newGameMode =
3928           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
3929             IcsPlayingWhite : IcsPlayingBlack;
3930         break;
3931       case RELATION_EXAMINING:
3932         newGameMode = IcsExamining;
3933         break;
3934       case RELATION_ISOLATED_BOARD:
3935       default:
3936         /* Just display this board.  If user was doing something else,
3937            we will forget about it until the next board comes. */
3938         newGameMode = IcsIdle;
3939         break;
3940       case RELATION_STARTING_POSITION:
3941         newGameMode = gameMode;
3942         break;
3943     }
3944
3945     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
3946          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
3947       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
3948       char *toSqr;
3949       for (k = 0; k < ranks; k++) {
3950         for (j = 0; j < files; j++)
3951           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
3952         if(gameInfo.holdingsWidth > 1) {
3953              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
3954              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
3955         }
3956       }
3957       CopyBoard(partnerBoard, board);
3958       if(toSqr = strchr(str, '/')) { // extract highlights from long move
3959         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
3960         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
3961       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
3962       if(toSqr = strchr(str, '-')) {
3963         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
3964         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
3965       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
3966       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
3967       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
3968       if(partnerUp) DrawPosition(FALSE, partnerBoard);
3969       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
3970       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
3971                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
3972       DisplayMessage(partnerStatus, "");
3973         partnerBoardValid = TRUE;
3974       return;
3975     }
3976
3977     /* Modify behavior for initial board display on move listing
3978        of wild games.
3979        */
3980     switch (ics_getting_history) {
3981       case H_FALSE:
3982       case H_REQUESTED:
3983         break;
3984       case H_GOT_REQ_HEADER:
3985       case H_GOT_UNREQ_HEADER:
3986         /* This is the initial position of the current game */
3987         gamenum = ics_gamenum;
3988         moveNum = 0;            /* old ICS bug workaround */
3989         if (to_play == 'B') {
3990           startedFromSetupPosition = TRUE;
3991           blackPlaysFirst = TRUE;
3992           moveNum = 1;
3993           if (forwardMostMove == 0) forwardMostMove = 1;
3994           if (backwardMostMove == 0) backwardMostMove = 1;
3995           if (currentMove == 0) currentMove = 1;
3996         }
3997         newGameMode = gameMode;
3998         relation = RELATION_STARTING_POSITION; /* ICC needs this */
3999         break;
4000       case H_GOT_UNWANTED_HEADER:
4001         /* This is an initial board that we don't want */
4002         return;
4003       case H_GETTING_MOVES:
4004         /* Should not happen */
4005         DisplayError(_("Error gathering move list: extra board"), 0);
4006         ics_getting_history = H_FALSE;
4007         return;
4008     }
4009
4010    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4011                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4012      /* [HGM] We seem to have switched variant unexpectedly
4013       * Try to guess new variant from board size
4014       */
4015           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4016           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4017           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4018           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4019           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4020           if(!weird) newVariant = VariantNormal;
4021           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4022           /* Get a move list just to see the header, which
4023              will tell us whether this is really bug or zh */
4024           if (ics_getting_history == H_FALSE) {
4025             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4026             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4027             SendToICS(str);
4028           }
4029     }
4030
4031     /* Take action if this is the first board of a new game, or of a
4032        different game than is currently being displayed.  */
4033     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4034         relation == RELATION_ISOLATED_BOARD) {
4035
4036         /* Forget the old game and get the history (if any) of the new one */
4037         if (gameMode != BeginningOfGame) {
4038           Reset(TRUE, TRUE);
4039         }
4040         newGame = TRUE;
4041         if (appData.autoRaiseBoard) BoardToTop();
4042         prevMove = -3;
4043         if (gamenum == -1) {
4044             newGameMode = IcsIdle;
4045         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4046                    appData.getMoveList && !reqFlag) {
4047             /* Need to get game history */
4048             ics_getting_history = H_REQUESTED;
4049             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4050             SendToICS(str);
4051         }
4052
4053         /* Initially flip the board to have black on the bottom if playing
4054            black or if the ICS flip flag is set, but let the user change
4055            it with the Flip View button. */
4056         flipView = appData.autoFlipView ?
4057           (newGameMode == IcsPlayingBlack) || ics_flip :
4058           appData.flipView;
4059
4060         /* Done with values from previous mode; copy in new ones */
4061         gameMode = newGameMode;
4062         ModeHighlight();
4063         ics_gamenum = gamenum;
4064         if (gamenum == gs_gamenum) {
4065             int klen = strlen(gs_kind);
4066             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4067             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4068             gameInfo.event = StrSave(str);
4069         } else {
4070             gameInfo.event = StrSave("ICS game");
4071         }
4072         gameInfo.site = StrSave(appData.icsHost);
4073         gameInfo.date = PGNDate();
4074         gameInfo.round = StrSave("-");
4075         gameInfo.white = StrSave(white);
4076         gameInfo.black = StrSave(black);
4077         timeControl = basetime * 60 * 1000;
4078         timeControl_2 = 0;
4079         timeIncrement = increment * 1000;
4080         movesPerSession = 0;
4081         gameInfo.timeControl = TimeControlTagValue();
4082         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4083   if (appData.debugMode) {
4084     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4085     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4086     setbuf(debugFP, NULL);
4087   }
4088
4089         gameInfo.outOfBook = NULL;
4090
4091         /* Do we have the ratings? */
4092         if (strcmp(player1Name, white) == 0 &&
4093             strcmp(player2Name, black) == 0) {
4094             if (appData.debugMode)
4095               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4096                       player1Rating, player2Rating);
4097             gameInfo.whiteRating = player1Rating;
4098             gameInfo.blackRating = player2Rating;
4099         } else if (strcmp(player2Name, white) == 0 &&
4100                    strcmp(player1Name, black) == 0) {
4101             if (appData.debugMode)
4102               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4103                       player2Rating, player1Rating);
4104             gameInfo.whiteRating = player2Rating;
4105             gameInfo.blackRating = player1Rating;
4106         }
4107         player1Name[0] = player2Name[0] = NULLCHAR;
4108
4109         /* Silence shouts if requested */
4110         if (appData.quietPlay &&
4111             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4112             SendToICS(ics_prefix);
4113             SendToICS("set shout 0\n");
4114         }
4115     }
4116
4117     /* Deal with midgame name changes */
4118     if (!newGame) {
4119         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4120             if (gameInfo.white) free(gameInfo.white);
4121             gameInfo.white = StrSave(white);
4122         }
4123         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4124             if (gameInfo.black) free(gameInfo.black);
4125             gameInfo.black = StrSave(black);
4126         }
4127     }
4128
4129     /* Throw away game result if anything actually changes in examine mode */
4130     if (gameMode == IcsExamining && !newGame) {
4131         gameInfo.result = GameUnfinished;
4132         if (gameInfo.resultDetails != NULL) {
4133             free(gameInfo.resultDetails);
4134             gameInfo.resultDetails = NULL;
4135         }
4136     }
4137
4138     /* In pausing && IcsExamining mode, we ignore boards coming
4139        in if they are in a different variation than we are. */
4140     if (pauseExamInvalid) return;
4141     if (pausing && gameMode == IcsExamining) {
4142         if (moveNum <= pauseExamForwardMostMove) {
4143             pauseExamInvalid = TRUE;
4144             forwardMostMove = pauseExamForwardMostMove;
4145             return;
4146         }
4147     }
4148
4149   if (appData.debugMode) {
4150     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4151   }
4152     /* Parse the board */
4153     for (k = 0; k < ranks; k++) {
4154       for (j = 0; j < files; j++)
4155         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4156       if(gameInfo.holdingsWidth > 1) {
4157            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4158            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4159       }
4160     }
4161     CopyBoard(boards[moveNum], board);
4162     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4163     if (moveNum == 0) {
4164         startedFromSetupPosition =
4165           !CompareBoards(board, initialPosition);
4166         if(startedFromSetupPosition)
4167             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4168     }
4169
4170     /* [HGM] Set castling rights. Take the outermost Rooks,
4171        to make it also work for FRC opening positions. Note that board12
4172        is really defective for later FRC positions, as it has no way to
4173        indicate which Rook can castle if they are on the same side of King.
4174        For the initial position we grant rights to the outermost Rooks,
4175        and remember thos rights, and we then copy them on positions
4176        later in an FRC game. This means WB might not recognize castlings with
4177        Rooks that have moved back to their original position as illegal,
4178        but in ICS mode that is not its job anyway.
4179     */
4180     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4181     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4182
4183         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4184             if(board[0][i] == WhiteRook) j = i;
4185         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4186         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4187             if(board[0][i] == WhiteRook) j = i;
4188         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4189         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4190             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4191         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4192         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4193             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4194         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4195
4196         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4197         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4198             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4199         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4200             if(board[BOARD_HEIGHT-1][k] == bKing)
4201                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4202         if(gameInfo.variant == VariantTwoKings) {
4203             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4204             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4205             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4206         }
4207     } else { int r;
4208         r = boards[moveNum][CASTLING][0] = initialRights[0];
4209         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4210         r = boards[moveNum][CASTLING][1] = initialRights[1];
4211         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4212         r = boards[moveNum][CASTLING][3] = initialRights[3];
4213         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4214         r = boards[moveNum][CASTLING][4] = initialRights[4];
4215         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4216         /* wildcastle kludge: always assume King has rights */
4217         r = boards[moveNum][CASTLING][2] = initialRights[2];
4218         r = boards[moveNum][CASTLING][5] = initialRights[5];
4219     }
4220     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4221     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4222
4223
4224     if (ics_getting_history == H_GOT_REQ_HEADER ||
4225         ics_getting_history == H_GOT_UNREQ_HEADER) {
4226         /* This was an initial position from a move list, not
4227            the current position */
4228         return;
4229     }
4230
4231     /* Update currentMove and known move number limits */
4232     newMove = newGame || moveNum > forwardMostMove;
4233
4234     if (newGame) {
4235         forwardMostMove = backwardMostMove = currentMove = moveNum;
4236         if (gameMode == IcsExamining && moveNum == 0) {
4237           /* Workaround for ICS limitation: we are not told the wild
4238              type when starting to examine a game.  But if we ask for
4239              the move list, the move list header will tell us */
4240             ics_getting_history = H_REQUESTED;
4241             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4242             SendToICS(str);
4243         }
4244     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4245                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4246 #if ZIPPY
4247         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4248         /* [HGM] applied this also to an engine that is silently watching        */
4249         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4250             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4251             gameInfo.variant == currentlyInitializedVariant) {
4252           takeback = forwardMostMove - moveNum;
4253           for (i = 0; i < takeback; i++) {
4254             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4255             SendToProgram("undo\n", &first);
4256           }
4257         }
4258 #endif
4259
4260         forwardMostMove = moveNum;
4261         if (!pausing || currentMove > forwardMostMove)
4262           currentMove = forwardMostMove;
4263     } else {
4264         /* New part of history that is not contiguous with old part */
4265         if (pausing && gameMode == IcsExamining) {
4266             pauseExamInvalid = TRUE;
4267             forwardMostMove = pauseExamForwardMostMove;
4268             return;
4269         }
4270         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4271 #if ZIPPY
4272             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4273                 // [HGM] when we will receive the move list we now request, it will be
4274                 // fed to the engine from the first move on. So if the engine is not
4275                 // in the initial position now, bring it there.
4276                 InitChessProgram(&first, 0);
4277             }
4278 #endif
4279             ics_getting_history = H_REQUESTED;
4280             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4281             SendToICS(str);
4282         }
4283         forwardMostMove = backwardMostMove = currentMove = moveNum;
4284     }
4285
4286     /* Update the clocks */
4287     if (strchr(elapsed_time, '.')) {
4288       /* Time is in ms */
4289       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4290       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4291     } else {
4292       /* Time is in seconds */
4293       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4294       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4295     }
4296
4297
4298 #if ZIPPY
4299     if (appData.zippyPlay && newGame &&
4300         gameMode != IcsObserving && gameMode != IcsIdle &&
4301         gameMode != IcsExamining)
4302       ZippyFirstBoard(moveNum, basetime, increment);
4303 #endif
4304
4305     /* Put the move on the move list, first converting
4306        to canonical algebraic form. */
4307     if (moveNum > 0) {
4308   if (appData.debugMode) {
4309     if (appData.debugMode) { int f = forwardMostMove;
4310         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4311                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4312                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4313     }
4314     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4315     fprintf(debugFP, "moveNum = %d\n", moveNum);
4316     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4317     setbuf(debugFP, NULL);
4318   }
4319         if (moveNum <= backwardMostMove) {
4320             /* We don't know what the board looked like before
4321                this move.  Punt. */
4322           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4323             strcat(parseList[moveNum - 1], " ");
4324             strcat(parseList[moveNum - 1], elapsed_time);
4325             moveList[moveNum - 1][0] = NULLCHAR;
4326         } else if (strcmp(move_str, "none") == 0) {
4327             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4328             /* Again, we don't know what the board looked like;
4329                this is really the start of the game. */
4330             parseList[moveNum - 1][0] = NULLCHAR;
4331             moveList[moveNum - 1][0] = NULLCHAR;
4332             backwardMostMove = moveNum;
4333             startedFromSetupPosition = TRUE;
4334             fromX = fromY = toX = toY = -1;
4335         } else {
4336           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4337           //                 So we parse the long-algebraic move string in stead of the SAN move
4338           int valid; char buf[MSG_SIZ], *prom;
4339
4340           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4341                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4342           // str looks something like "Q/a1-a2"; kill the slash
4343           if(str[1] == '/')
4344             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4345           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4346           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4347                 strcat(buf, prom); // long move lacks promo specification!
4348           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4349                 if(appData.debugMode)
4350                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4351                 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4352           }
4353           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4354                                 &fromX, &fromY, &toX, &toY, &promoChar)
4355                || ParseOneMove(buf, moveNum - 1, &moveType,
4356                                 &fromX, &fromY, &toX, &toY, &promoChar);
4357           // end of long SAN patch
4358           if (valid) {
4359             (void) CoordsToAlgebraic(boards[moveNum - 1],
4360                                      PosFlags(moveNum - 1),
4361                                      fromY, fromX, toY, toX, promoChar,
4362                                      parseList[moveNum-1]);
4363             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4364               case MT_NONE:
4365               case MT_STALEMATE:
4366               default:
4367                 break;
4368               case MT_CHECK:
4369                 if(gameInfo.variant != VariantShogi)
4370                     strcat(parseList[moveNum - 1], "+");
4371                 break;
4372               case MT_CHECKMATE:
4373               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4374                 strcat(parseList[moveNum - 1], "#");
4375                 break;
4376             }
4377             strcat(parseList[moveNum - 1], " ");
4378             strcat(parseList[moveNum - 1], elapsed_time);
4379             /* currentMoveString is set as a side-effect of ParseOneMove */
4380             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '+';
4381             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4382             strcat(moveList[moveNum - 1], "\n");
4383
4384             if(gameInfo.holdingsWidth && !appData.disguise) // inherit info that ICS does not give from previous board
4385               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4386                 ChessSquare old, new = boards[moveNum][k][j];
4387                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4388                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4389                   if(old == new) continue;
4390                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4391                   else if(new == WhiteWazir || new == BlackWazir) {
4392                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4393                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4394                       else boards[moveNum][k][j] = old; // preserve type of Gold
4395                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4396                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4397               }
4398           } else {
4399             /* Move from ICS was illegal!?  Punt. */
4400             if (appData.debugMode) {
4401               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4402               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4403             }
4404             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4405             strcat(parseList[moveNum - 1], " ");
4406             strcat(parseList[moveNum - 1], elapsed_time);
4407             moveList[moveNum - 1][0] = NULLCHAR;
4408             fromX = fromY = toX = toY = -1;
4409           }
4410         }
4411   if (appData.debugMode) {
4412     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4413     setbuf(debugFP, NULL);
4414   }
4415
4416 #if ZIPPY
4417         /* Send move to chess program (BEFORE animating it). */
4418         if (appData.zippyPlay && !newGame && newMove &&
4419            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4420
4421             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4422                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4423                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4424                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4425                             move_str);
4426                     DisplayError(str, 0);
4427                 } else {
4428                     if (first.sendTime) {
4429                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4430                     }
4431                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4432                     if (firstMove && !bookHit) {
4433                         firstMove = FALSE;
4434                         if (first.useColors) {
4435                           SendToProgram(gameMode == IcsPlayingWhite ?
4436                                         "white\ngo\n" :
4437                                         "black\ngo\n", &first);
4438                         } else {
4439                           SendToProgram("go\n", &first);
4440                         }
4441                         first.maybeThinking = TRUE;
4442                     }
4443                 }
4444             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4445               if (moveList[moveNum - 1][0] == NULLCHAR) {
4446                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4447                 DisplayError(str, 0);
4448               } else {
4449                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4450                 SendMoveToProgram(moveNum - 1, &first);
4451               }
4452             }
4453         }
4454 #endif
4455     }
4456
4457     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4458         /* If move comes from a remote source, animate it.  If it
4459            isn't remote, it will have already been animated. */
4460         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4461             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4462         }
4463         if (!pausing && appData.highlightLastMove) {
4464             SetHighlights(fromX, fromY, toX, toY);
4465         }
4466     }
4467
4468     /* Start the clocks */
4469     whiteFlag = blackFlag = FALSE;
4470     appData.clockMode = !(basetime == 0 && increment == 0);
4471     if (ticking == 0) {
4472       ics_clock_paused = TRUE;
4473       StopClocks();
4474     } else if (ticking == 1) {
4475       ics_clock_paused = FALSE;
4476     }
4477     if (gameMode == IcsIdle ||
4478         relation == RELATION_OBSERVING_STATIC ||
4479         relation == RELATION_EXAMINING ||
4480         ics_clock_paused)
4481       DisplayBothClocks();
4482     else
4483       StartClocks();
4484
4485     /* Display opponents and material strengths */
4486     if (gameInfo.variant != VariantBughouse &&
4487         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4488         if (tinyLayout || smallLayout) {
4489             if(gameInfo.variant == VariantNormal)
4490               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4491                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4492                     basetime, increment);
4493             else
4494               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4495                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4496                     basetime, increment, (int) gameInfo.variant);
4497         } else {
4498             if(gameInfo.variant == VariantNormal)
4499               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4500                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4501                     basetime, increment);
4502             else
4503               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4504                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4505                     basetime, increment, VariantName(gameInfo.variant));
4506         }
4507         DisplayTitle(str);
4508   if (appData.debugMode) {
4509     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4510   }
4511     }
4512
4513
4514     /* Display the board */
4515     if (!pausing && !appData.noGUI) {
4516
4517       if (appData.premove)
4518           if (!gotPremove ||
4519              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4520              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4521               ClearPremoveHighlights();
4522
4523       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4524         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4525       DrawPosition(j, boards[currentMove]);
4526
4527       DisplayMove(moveNum - 1);
4528       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4529             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4530               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4531         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4532       }
4533     }
4534
4535     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4536 #if ZIPPY
4537     if(bookHit) { // [HGM] book: simulate book reply
4538         static char bookMove[MSG_SIZ]; // a bit generous?
4539
4540         programStats.nodes = programStats.depth = programStats.time =
4541         programStats.score = programStats.got_only_move = 0;
4542         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4543
4544         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4545         strcat(bookMove, bookHit);
4546         HandleMachineMove(bookMove, &first);
4547     }
4548 #endif
4549 }
4550
4551 void
4552 GetMoveListEvent()
4553 {
4554     char buf[MSG_SIZ];
4555     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4556         ics_getting_history = H_REQUESTED;
4557         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4558         SendToICS(buf);
4559     }
4560 }
4561
4562 void
4563 AnalysisPeriodicEvent(force)
4564      int force;
4565 {
4566     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4567          && !force) || !appData.periodicUpdates)
4568       return;
4569
4570     /* Send . command to Crafty to collect stats */
4571     SendToProgram(".\n", &first);
4572
4573     /* Don't send another until we get a response (this makes
4574        us stop sending to old Crafty's which don't understand
4575        the "." command (sending illegal cmds resets node count & time,
4576        which looks bad)) */
4577     programStats.ok_to_send = 0;
4578 }
4579
4580 void ics_update_width(new_width)
4581         int new_width;
4582 {
4583         ics_printf("set width %d\n", new_width);
4584 }
4585
4586 void
4587 SendMoveToProgram(moveNum, cps)
4588      int moveNum;
4589      ChessProgramState *cps;
4590 {
4591     char buf[MSG_SIZ];
4592
4593     if (cps->useUsermove) {
4594       SendToProgram("usermove ", cps);
4595     }
4596     if (cps->useSAN) {
4597       char *space;
4598       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4599         int len = space - parseList[moveNum];
4600         memcpy(buf, parseList[moveNum], len);
4601         buf[len++] = '\n';
4602         buf[len] = NULLCHAR;
4603       } else {
4604         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4605       }
4606       SendToProgram(buf, cps);
4607     } else {
4608       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4609         AlphaRank(moveList[moveNum], 4);
4610         SendToProgram(moveList[moveNum], cps);
4611         AlphaRank(moveList[moveNum], 4); // and back
4612       } else
4613       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4614        * the engine. It would be nice to have a better way to identify castle
4615        * moves here. */
4616       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4617                                                                          && cps->useOOCastle) {
4618         int fromX = moveList[moveNum][0] - AAA;
4619         int fromY = moveList[moveNum][1] - ONE;
4620         int toX = moveList[moveNum][2] - AAA;
4621         int toY = moveList[moveNum][3] - ONE;
4622         if((boards[moveNum][fromY][fromX] == WhiteKing
4623             && boards[moveNum][toY][toX] == WhiteRook)
4624            || (boards[moveNum][fromY][fromX] == BlackKing
4625                && boards[moveNum][toY][toX] == BlackRook)) {
4626           if(toX > fromX) SendToProgram("O-O\n", cps);
4627           else SendToProgram("O-O-O\n", cps);
4628         }
4629         else SendToProgram(moveList[moveNum], cps);
4630       }
4631       else SendToProgram(moveList[moveNum], cps);
4632       /* End of additions by Tord */
4633     }
4634
4635     /* [HGM] setting up the opening has brought engine in force mode! */
4636     /*       Send 'go' if we are in a mode where machine should play. */
4637     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4638         (gameMode == TwoMachinesPlay   ||
4639 #if ZIPPY
4640          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4641 #endif
4642          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4643         SendToProgram("go\n", cps);
4644   if (appData.debugMode) {
4645     fprintf(debugFP, "(extra)\n");
4646   }
4647     }
4648     setboardSpoiledMachineBlack = 0;
4649 }
4650
4651 void
4652 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4653      ChessMove moveType;
4654      int fromX, fromY, toX, toY;
4655      char promoChar;
4656 {
4657     char user_move[MSG_SIZ];
4658
4659     switch (moveType) {
4660       default:
4661         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4662                 (int)moveType, fromX, fromY, toX, toY);
4663         DisplayError(user_move + strlen("say "), 0);
4664         break;
4665       case WhiteKingSideCastle:
4666       case BlackKingSideCastle:
4667       case WhiteQueenSideCastleWild:
4668       case BlackQueenSideCastleWild:
4669       /* PUSH Fabien */
4670       case WhiteHSideCastleFR:
4671       case BlackHSideCastleFR:
4672       /* POP Fabien */
4673         snprintf(user_move, MSG_SIZ, "o-o\n");
4674         break;
4675       case WhiteQueenSideCastle:
4676       case BlackQueenSideCastle:
4677       case WhiteKingSideCastleWild:
4678       case BlackKingSideCastleWild:
4679       /* PUSH Fabien */
4680       case WhiteASideCastleFR:
4681       case BlackASideCastleFR:
4682       /* POP Fabien */
4683         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4684         break;
4685       case WhiteNonPromotion:
4686       case BlackNonPromotion:
4687         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4688         break;
4689       case WhitePromotion:
4690       case BlackPromotion:
4691         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4692           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4693                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4694                 PieceToChar(WhiteFerz));
4695         else if(gameInfo.variant == VariantGreat)
4696           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4697                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4698                 PieceToChar(WhiteMan));
4699         else
4700           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4701                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4702                 promoChar);
4703         break;
4704       case WhiteDrop:
4705       case BlackDrop:
4706       drop:
4707         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4708                  ToUpper(PieceToChar((ChessSquare) fromX)),
4709                  AAA + toX, ONE + toY);
4710         break;
4711       case IllegalMove:  /* could be a variant we don't quite understand */
4712         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4713       case NormalMove:
4714       case WhiteCapturesEnPassant:
4715       case BlackCapturesEnPassant:
4716         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4717                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4718         break;
4719     }
4720     SendToICS(user_move);
4721     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4722         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4723 }
4724
4725 void
4726 UploadGameEvent()
4727 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4728     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4729     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4730     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4731         DisplayError("You cannot do this while you are playing or observing", 0);
4732         return;
4733     }
4734     if(gameMode != IcsExamining) { // is this ever not the case?
4735         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4736
4737         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4738           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4739         } else { // on FICS we must first go to general examine mode
4740           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4741         }
4742         if(gameInfo.variant != VariantNormal) {
4743             // try figure out wild number, as xboard names are not always valid on ICS
4744             for(i=1; i<=36; i++) {
4745               snprintf(buf, MSG_SIZ, "wild/%d", i);
4746                 if(StringToVariant(buf) == gameInfo.variant) break;
4747             }
4748             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4749             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4750             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4751         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4752         SendToICS(ics_prefix);
4753         SendToICS(buf);
4754         if(startedFromSetupPosition || backwardMostMove != 0) {
4755           fen = PositionToFEN(backwardMostMove, NULL);
4756           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4757             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4758             SendToICS(buf);
4759           } else { // FICS: everything has to set by separate bsetup commands
4760             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4761             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4762             SendToICS(buf);
4763             if(!WhiteOnMove(backwardMostMove)) {
4764                 SendToICS("bsetup tomove black\n");
4765             }
4766             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4767             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4768             SendToICS(buf);
4769             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4770             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4771             SendToICS(buf);
4772             i = boards[backwardMostMove][EP_STATUS];
4773             if(i >= 0) { // set e.p.
4774               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4775                 SendToICS(buf);
4776             }
4777             bsetup++;
4778           }
4779         }
4780       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4781             SendToICS("bsetup done\n"); // switch to normal examining.
4782     }
4783     for(i = backwardMostMove; i<last; i++) {
4784         char buf[20];
4785         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4786         SendToICS(buf);
4787     }
4788     SendToICS(ics_prefix);
4789     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4790 }
4791
4792 void
4793 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4794      int rf, ff, rt, ft;
4795      char promoChar;
4796      char move[7];
4797 {
4798     if (rf == DROP_RANK) {
4799       sprintf(move, "%c@%c%c\n",
4800                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4801     } else {
4802         if (promoChar == 'x' || promoChar == NULLCHAR) {
4803           sprintf(move, "%c%c%c%c\n",
4804                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4805         } else {
4806             sprintf(move, "%c%c%c%c%c\n",
4807                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4808         }
4809     }
4810 }
4811
4812 void
4813 ProcessICSInitScript(f)
4814      FILE *f;
4815 {
4816     char buf[MSG_SIZ];
4817
4818     while (fgets(buf, MSG_SIZ, f)) {
4819         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4820     }
4821
4822     fclose(f);
4823 }
4824
4825
4826 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4827 void
4828 AlphaRank(char *move, int n)
4829 {
4830 //    char *p = move, c; int x, y;
4831
4832     if (appData.debugMode) {
4833         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4834     }
4835
4836     if(move[1]=='*' &&
4837        move[2]>='0' && move[2]<='9' &&
4838        move[3]>='a' && move[3]<='x'    ) {
4839         move[1] = '@';
4840         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4841         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4842     } else
4843     if(move[0]>='0' && move[0]<='9' &&
4844        move[1]>='a' && move[1]<='x' &&
4845        move[2]>='0' && move[2]<='9' &&
4846        move[3]>='a' && move[3]<='x'    ) {
4847         /* input move, Shogi -> normal */
4848         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4849         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4850         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4851         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4852     } else
4853     if(move[1]=='@' &&
4854        move[3]>='0' && move[3]<='9' &&
4855        move[2]>='a' && move[2]<='x'    ) {
4856         move[1] = '*';
4857         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4858         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4859     } else
4860     if(
4861        move[0]>='a' && move[0]<='x' &&
4862        move[3]>='0' && move[3]<='9' &&
4863        move[2]>='a' && move[2]<='x'    ) {
4864          /* output move, normal -> Shogi */
4865         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4866         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4867         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4868         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4869         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4870     }
4871     if (appData.debugMode) {
4872         fprintf(debugFP, "   out = '%s'\n", move);
4873     }
4874 }
4875
4876 char yy_textstr[8000];
4877
4878 /* Parser for moves from gnuchess, ICS, or user typein box */
4879 Boolean
4880 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4881      char *move;
4882      int moveNum;
4883      ChessMove *moveType;
4884      int *fromX, *fromY, *toX, *toY;
4885      char *promoChar;
4886 {
4887     if (appData.debugMode) {
4888         fprintf(debugFP, "move to parse: %s\n", move);
4889     }
4890     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4891
4892     switch (*moveType) {
4893       case WhitePromotion:
4894       case BlackPromotion:
4895       case WhiteNonPromotion:
4896       case BlackNonPromotion:
4897       case NormalMove:
4898       case WhiteCapturesEnPassant:
4899       case BlackCapturesEnPassant:
4900       case WhiteKingSideCastle:
4901       case WhiteQueenSideCastle:
4902       case BlackKingSideCastle:
4903       case BlackQueenSideCastle:
4904       case WhiteKingSideCastleWild:
4905       case WhiteQueenSideCastleWild:
4906       case BlackKingSideCastleWild:
4907       case BlackQueenSideCastleWild:
4908       /* Code added by Tord: */
4909       case WhiteHSideCastleFR:
4910       case WhiteASideCastleFR:
4911       case BlackHSideCastleFR:
4912       case BlackASideCastleFR:
4913       /* End of code added by Tord */
4914       case IllegalMove:         /* bug or odd chess variant */
4915         *fromX = currentMoveString[0] - AAA;
4916         *fromY = currentMoveString[1] - ONE;
4917         *toX = currentMoveString[2] - AAA;
4918         *toY = currentMoveString[3] - ONE;
4919         *promoChar = currentMoveString[4];
4920         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4921             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4922     if (appData.debugMode) {
4923         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4924     }
4925             *fromX = *fromY = *toX = *toY = 0;
4926             return FALSE;
4927         }
4928         if (appData.testLegality) {
4929           return (*moveType != IllegalMove);
4930         } else {
4931           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4932                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4933         }
4934
4935       case WhiteDrop:
4936       case BlackDrop:
4937         *fromX = *moveType == WhiteDrop ?
4938           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4939           (int) CharToPiece(ToLower(currentMoveString[0]));
4940         *fromY = DROP_RANK;
4941         *toX = currentMoveString[2] - AAA;
4942         *toY = currentMoveString[3] - ONE;
4943         *promoChar = NULLCHAR;
4944         return TRUE;
4945
4946       case AmbiguousMove:
4947       case ImpossibleMove:
4948       case EndOfFile:
4949       case ElapsedTime:
4950       case Comment:
4951       case PGNTag:
4952       case NAG:
4953       case WhiteWins:
4954       case BlackWins:
4955       case GameIsDrawn:
4956       default:
4957     if (appData.debugMode) {
4958         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4959     }
4960         /* bug? */
4961         *fromX = *fromY = *toX = *toY = 0;
4962         *promoChar = NULLCHAR;
4963         return FALSE;
4964     }
4965 }
4966
4967
4968 void
4969 ParsePV(char *pv, Boolean storeComments)
4970 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4971   int fromX, fromY, toX, toY; char promoChar;
4972   ChessMove moveType;
4973   Boolean valid;
4974   int nr = 0;
4975
4976   endPV = forwardMostMove;
4977   do {
4978     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4979     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4980     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4981 if(appData.debugMode){
4982 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
4983 }
4984     if(!valid && nr == 0 &&
4985        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4986         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4987         // Hande case where played move is different from leading PV move
4988         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4989         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4990         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4991         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4992           endPV += 2; // if position different, keep this
4993           moveList[endPV-1][0] = fromX + AAA;
4994           moveList[endPV-1][1] = fromY + ONE;
4995           moveList[endPV-1][2] = toX + AAA;
4996           moveList[endPV-1][3] = toY + ONE;
4997           parseList[endPV-1][0] = NULLCHAR;
4998           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4999         }
5000       }
5001     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5002     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5003     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5004     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5005         valid++; // allow comments in PV
5006         continue;
5007     }
5008     nr++;
5009     if(endPV+1 > framePtr) break; // no space, truncate
5010     if(!valid) break;
5011     endPV++;
5012     CopyBoard(boards[endPV], boards[endPV-1]);
5013     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5014     moveList[endPV-1][0] = fromX + AAA;
5015     moveList[endPV-1][1] = fromY + ONE;
5016     moveList[endPV-1][2] = toX + AAA;
5017     moveList[endPV-1][3] = toY + ONE;
5018     if(storeComments)
5019         CoordsToAlgebraic(boards[endPV - 1],
5020                              PosFlags(endPV - 1),
5021                              fromY, fromX, toY, toX, promoChar,
5022                              parseList[endPV - 1]);
5023     else
5024         parseList[endPV-1][0] = NULLCHAR;
5025   } while(valid);
5026   currentMove = endPV;
5027   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5028   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5029                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5030   DrawPosition(TRUE, boards[currentMove]);
5031 }
5032
5033 static int lastX, lastY;
5034
5035 Boolean
5036 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5037 {
5038         int startPV;
5039         char *p;
5040
5041         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5042         lastX = x; lastY = y;
5043         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5044         startPV = index;
5045         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5046         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5047         index = startPV;
5048         do{ while(buf[index] && buf[index] != '\n') index++;
5049         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5050         buf[index] = 0;
5051         ParsePV(buf+startPV, FALSE);
5052         *start = startPV; *end = index-1;
5053         return TRUE;
5054 }
5055
5056 Boolean
5057 LoadPV(int x, int y)
5058 { // called on right mouse click to load PV
5059   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5060   lastX = x; lastY = y;
5061   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5062   return TRUE;
5063 }
5064
5065 void
5066 UnLoadPV()
5067 {
5068   if(endPV < 0) return;
5069   endPV = -1;
5070   currentMove = forwardMostMove;
5071   ClearPremoveHighlights();
5072   DrawPosition(TRUE, boards[currentMove]);
5073 }
5074
5075 void
5076 MovePV(int x, int y, int h)
5077 { // step through PV based on mouse coordinates (called on mouse move)
5078   int margin = h>>3, step = 0;
5079
5080   if(endPV < 0) return;
5081   // we must somehow check if right button is still down (might be released off board!)
5082   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5083   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5084   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5085   if(!step) return;
5086   lastX = x; lastY = y;
5087   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5088   currentMove += step;
5089   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5090   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5091                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5092   DrawPosition(FALSE, boards[currentMove]);
5093 }
5094
5095
5096 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5097 // All positions will have equal probability, but the current method will not provide a unique
5098 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5099 #define DARK 1
5100 #define LITE 2
5101 #define ANY 3
5102
5103 int squaresLeft[4];
5104 int piecesLeft[(int)BlackPawn];
5105 int seed, nrOfShuffles;
5106
5107 void GetPositionNumber()
5108 {       // sets global variable seed
5109         int i;
5110
5111         seed = appData.defaultFrcPosition;
5112         if(seed < 0) { // randomize based on time for negative FRC position numbers
5113                 for(i=0; i<50; i++) seed += random();
5114                 seed = random() ^ random() >> 8 ^ random() << 8;
5115                 if(seed<0) seed = -seed;
5116         }
5117 }
5118
5119 int put(Board board, int pieceType, int rank, int n, int shade)
5120 // put the piece on the (n-1)-th empty squares of the given shade
5121 {
5122         int i;
5123
5124         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5125                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5126                         board[rank][i] = (ChessSquare) pieceType;
5127                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5128                         squaresLeft[ANY]--;
5129                         piecesLeft[pieceType]--;
5130                         return i;
5131                 }
5132         }
5133         return -1;
5134 }
5135
5136
5137 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5138 // calculate where the next piece goes, (any empty square), and put it there
5139 {
5140         int i;
5141
5142         i = seed % squaresLeft[shade];
5143         nrOfShuffles *= squaresLeft[shade];
5144         seed /= squaresLeft[shade];
5145         put(board, pieceType, rank, i, shade);
5146 }
5147
5148 void AddTwoPieces(Board board, int pieceType, int rank)
5149 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5150 {
5151         int i, n=squaresLeft[ANY], j=n-1, k;
5152
5153         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5154         i = seed % k;  // pick one
5155         nrOfShuffles *= k;
5156         seed /= k;
5157         while(i >= j) i -= j--;
5158         j = n - 1 - j; i += j;
5159         put(board, pieceType, rank, j, ANY);
5160         put(board, pieceType, rank, i, ANY);
5161 }
5162
5163 void SetUpShuffle(Board board, int number)
5164 {
5165         int i, p, first=1;
5166
5167         GetPositionNumber(); nrOfShuffles = 1;
5168
5169         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5170         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5171         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5172
5173         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5174
5175         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5176             p = (int) board[0][i];
5177             if(p < (int) BlackPawn) piecesLeft[p] ++;
5178             board[0][i] = EmptySquare;
5179         }
5180
5181         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5182             // shuffles restricted to allow normal castling put KRR first
5183             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5184                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5185             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5186                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5187             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5188                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5189             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5190                 put(board, WhiteRook, 0, 0, ANY);
5191             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5192         }
5193
5194         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5195             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5196             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5197                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5198                 while(piecesLeft[p] >= 2) {
5199                     AddOnePiece(board, p, 0, LITE);
5200                     AddOnePiece(board, p, 0, DARK);
5201                 }
5202                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5203             }
5204
5205         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5206             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5207             // but we leave King and Rooks for last, to possibly obey FRC restriction
5208             if(p == (int)WhiteRook) continue;
5209             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5210             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5211         }
5212
5213         // now everything is placed, except perhaps King (Unicorn) and Rooks
5214
5215         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5216             // Last King gets castling rights
5217             while(piecesLeft[(int)WhiteUnicorn]) {
5218                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5219                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5220             }
5221
5222             while(piecesLeft[(int)WhiteKing]) {
5223                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5224                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5225             }
5226
5227
5228         } else {
5229             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5230             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5231         }
5232
5233         // Only Rooks can be left; simply place them all
5234         while(piecesLeft[(int)WhiteRook]) {
5235                 i = put(board, WhiteRook, 0, 0, ANY);
5236                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5237                         if(first) {
5238                                 first=0;
5239                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5240                         }
5241                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5242                 }
5243         }
5244         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5245             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5246         }
5247
5248         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5249 }
5250
5251 int SetCharTable( char *table, const char * map )
5252 /* [HGM] moved here from winboard.c because of its general usefulness */
5253 /*       Basically a safe strcpy that uses the last character as King */
5254 {
5255     int result = FALSE; int NrPieces;
5256
5257     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5258                     && NrPieces >= 12 && !(NrPieces&1)) {
5259         int i; /* [HGM] Accept even length from 12 to 34 */
5260
5261         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5262         for( i=0; i<NrPieces/2-1; i++ ) {
5263             table[i] = map[i];
5264             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5265         }
5266         table[(int) WhiteKing]  = map[NrPieces/2-1];
5267         table[(int) BlackKing]  = map[NrPieces-1];
5268
5269         result = TRUE;
5270     }
5271
5272     return result;
5273 }
5274
5275 void Prelude(Board board)
5276 {       // [HGM] superchess: random selection of exo-pieces
5277         int i, j, k; ChessSquare p;
5278         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5279
5280         GetPositionNumber(); // use FRC position number
5281
5282         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5283             SetCharTable(pieceToChar, appData.pieceToCharTable);
5284             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5285                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5286         }
5287
5288         j = seed%4;                 seed /= 4;
5289         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5290         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5291         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5292         j = seed%3 + (seed%3 >= j); seed /= 3;
5293         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5294         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5295         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5296         j = seed%3;                 seed /= 3;
5297         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5298         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5299         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5300         j = seed%2 + (seed%2 >= j); seed /= 2;
5301         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5302         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5303         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5304         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5305         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5306         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5307         put(board, exoPieces[0],    0, 0, ANY);
5308         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5309 }
5310
5311 void
5312 InitPosition(redraw)
5313      int redraw;
5314 {
5315     ChessSquare (* pieces)[BOARD_FILES];
5316     int i, j, pawnRow, overrule,
5317     oldx = gameInfo.boardWidth,
5318     oldy = gameInfo.boardHeight,
5319     oldh = gameInfo.holdingsWidth,
5320     oldv = gameInfo.variant;
5321
5322     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5323
5324     /* [AS] Initialize pv info list [HGM] and game status */
5325     {
5326         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5327             pvInfoList[i].depth = 0;
5328             boards[i][EP_STATUS] = EP_NONE;
5329             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5330         }
5331
5332         initialRulePlies = 0; /* 50-move counter start */
5333
5334         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5335         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5336     }
5337
5338
5339     /* [HGM] logic here is completely changed. In stead of full positions */
5340     /* the initialized data only consist of the two backranks. The switch */
5341     /* selects which one we will use, which is than copied to the Board   */
5342     /* initialPosition, which for the rest is initialized by Pawns and    */
5343     /* empty squares. This initial position is then copied to boards[0],  */
5344     /* possibly after shuffling, so that it remains available.            */
5345
5346     gameInfo.holdingsWidth = 0; /* default board sizes */
5347     gameInfo.boardWidth    = 8;
5348     gameInfo.boardHeight   = 8;
5349     gameInfo.holdingsSize  = 0;
5350     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5351     for(i=0; i<BOARD_FILES-2; i++)
5352       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5353     initialPosition[EP_STATUS] = EP_NONE;
5354     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5355     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5356          SetCharTable(pieceNickName, appData.pieceNickNames);
5357     else SetCharTable(pieceNickName, "............");
5358
5359     switch (gameInfo.variant) {
5360     case VariantFischeRandom:
5361       shuffleOpenings = TRUE;
5362     default:
5363       pieces = FIDEArray;
5364       break;
5365     case VariantShatranj:
5366       pieces = ShatranjArray;
5367       nrCastlingRights = 0;
5368       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5369       break;
5370     case VariantMakruk:
5371       pieces = makrukArray;
5372       nrCastlingRights = 0;
5373       startedFromSetupPosition = TRUE;
5374       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5375       break;
5376     case VariantTwoKings:
5377       pieces = twoKingsArray;
5378       break;
5379     case VariantCapaRandom:
5380       shuffleOpenings = TRUE;
5381     case VariantCapablanca:
5382       pieces = CapablancaArray;
5383       gameInfo.boardWidth = 10;
5384       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5385       break;
5386     case VariantGothic:
5387       pieces = GothicArray;
5388       gameInfo.boardWidth = 10;
5389       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5390       break;
5391     case VariantJanus:
5392       pieces = JanusArray;
5393       gameInfo.boardWidth = 10;
5394       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5395       nrCastlingRights = 6;
5396         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5397         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5398         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5399         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5400         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5401         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5402       break;
5403     case VariantFalcon:
5404       pieces = FalconArray;
5405       gameInfo.boardWidth = 10;
5406       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5407       break;
5408     case VariantXiangqi:
5409       pieces = XiangqiArray;
5410       gameInfo.boardWidth  = 9;
5411       gameInfo.boardHeight = 10;
5412       nrCastlingRights = 0;
5413       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5414       break;
5415     case VariantShogi:
5416       pieces = ShogiArray;
5417       gameInfo.boardWidth  = 9;
5418       gameInfo.boardHeight = 9;
5419       gameInfo.holdingsSize = 7;
5420       nrCastlingRights = 0;
5421       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5422       break;
5423     case VariantCourier:
5424       pieces = CourierArray;
5425       gameInfo.boardWidth  = 12;
5426       nrCastlingRights = 0;
5427       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5428       break;
5429     case VariantKnightmate:
5430       pieces = KnightmateArray;
5431       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5432       break;
5433     case VariantFairy:
5434       pieces = fairyArray;
5435       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5436       break;
5437     case VariantGreat:
5438       pieces = GreatArray;
5439       gameInfo.boardWidth = 10;
5440       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5441       gameInfo.holdingsSize = 8;
5442       break;
5443     case VariantSuper:
5444       pieces = FIDEArray;
5445       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5446       gameInfo.holdingsSize = 8;
5447       startedFromSetupPosition = TRUE;
5448       break;
5449     case VariantCrazyhouse:
5450     case VariantBughouse:
5451       pieces = FIDEArray;
5452       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5453       gameInfo.holdingsSize = 5;
5454       break;
5455     case VariantWildCastle:
5456       pieces = FIDEArray;
5457       /* !!?shuffle with kings guaranteed to be on d or e file */
5458       shuffleOpenings = 1;
5459       break;
5460     case VariantNoCastle:
5461       pieces = FIDEArray;
5462       nrCastlingRights = 0;
5463       /* !!?unconstrained back-rank shuffle */
5464       shuffleOpenings = 1;
5465       break;
5466     }
5467
5468     overrule = 0;
5469     if(appData.NrFiles >= 0) {
5470         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5471         gameInfo.boardWidth = appData.NrFiles;
5472     }
5473     if(appData.NrRanks >= 0) {
5474         gameInfo.boardHeight = appData.NrRanks;
5475     }
5476     if(appData.holdingsSize >= 0) {
5477         i = appData.holdingsSize;
5478         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5479         gameInfo.holdingsSize = i;
5480     }
5481     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5482     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5483         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5484
5485     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5486     if(pawnRow < 1) pawnRow = 1;
5487     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5488
5489     /* User pieceToChar list overrules defaults */
5490     if(appData.pieceToCharTable != NULL)
5491         SetCharTable(pieceToChar, appData.pieceToCharTable);
5492
5493     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5494
5495         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5496             s = (ChessSquare) 0; /* account holding counts in guard band */
5497         for( i=0; i<BOARD_HEIGHT; i++ )
5498             initialPosition[i][j] = s;
5499
5500         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5501         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5502         initialPosition[pawnRow][j] = WhitePawn;
5503         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5504         if(gameInfo.variant == VariantXiangqi) {
5505             if(j&1) {
5506                 initialPosition[pawnRow][j] =
5507                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5508                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5509                    initialPosition[2][j] = WhiteCannon;
5510                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5511                 }
5512             }
5513         }
5514         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5515     }
5516     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5517
5518             j=BOARD_LEFT+1;
5519             initialPosition[1][j] = WhiteBishop;
5520             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5521             j=BOARD_RGHT-2;
5522             initialPosition[1][j] = WhiteRook;
5523             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5524     }
5525
5526     if( nrCastlingRights == -1) {
5527         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5528         /*       This sets default castling rights from none to normal corners   */
5529         /* Variants with other castling rights must set them themselves above    */
5530         nrCastlingRights = 6;
5531
5532         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5533         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5534         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5535         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5536         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5537         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5538      }
5539
5540      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5541      if(gameInfo.variant == VariantGreat) { // promotion commoners
5542         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5543         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5544         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5545         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5546      }
5547   if (appData.debugMode) {
5548     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5549   }
5550     if(shuffleOpenings) {
5551         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5552         startedFromSetupPosition = TRUE;
5553     }
5554     if(startedFromPositionFile) {
5555       /* [HGM] loadPos: use PositionFile for every new game */
5556       CopyBoard(initialPosition, filePosition);
5557       for(i=0; i<nrCastlingRights; i++)
5558           initialRights[i] = filePosition[CASTLING][i];
5559       startedFromSetupPosition = TRUE;
5560     }
5561
5562     CopyBoard(boards[0], initialPosition);
5563
5564     if(oldx != gameInfo.boardWidth ||
5565        oldy != gameInfo.boardHeight ||
5566        oldh != gameInfo.holdingsWidth
5567 #ifdef GOTHIC
5568        || oldv == VariantGothic ||        // For licensing popups
5569        gameInfo.variant == VariantGothic
5570 #endif
5571 #ifdef FALCON
5572        || oldv == VariantFalcon ||
5573        gameInfo.variant == VariantFalcon
5574 #endif
5575                                          )
5576             InitDrawingSizes(-2 ,0);
5577
5578     if (redraw)
5579       DrawPosition(TRUE, boards[currentMove]);
5580 }
5581
5582 void
5583 SendBoard(cps, moveNum)
5584      ChessProgramState *cps;
5585      int moveNum;
5586 {
5587     char message[MSG_SIZ];
5588
5589     if (cps->useSetboard) {
5590       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5591       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5592       SendToProgram(message, cps);
5593       free(fen);
5594
5595     } else {
5596       ChessSquare *bp;
5597       int i, j;
5598       /* Kludge to set black to move, avoiding the troublesome and now
5599        * deprecated "black" command.
5600        */
5601       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5602
5603       SendToProgram("edit\n", cps);
5604       SendToProgram("#\n", cps);
5605       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5606         bp = &boards[moveNum][i][BOARD_LEFT];
5607         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5608           if ((int) *bp < (int) BlackPawn) {
5609             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5610                     AAA + j, ONE + i);
5611             if(message[0] == '+' || message[0] == '~') {
5612               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5613                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5614                         AAA + j, ONE + i);
5615             }
5616             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5617                 message[1] = BOARD_RGHT   - 1 - j + '1';
5618                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5619             }
5620             SendToProgram(message, cps);
5621           }
5622         }
5623       }
5624
5625       SendToProgram("c\n", cps);
5626       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5627         bp = &boards[moveNum][i][BOARD_LEFT];
5628         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5629           if (((int) *bp != (int) EmptySquare)
5630               && ((int) *bp >= (int) BlackPawn)) {
5631             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5632                     AAA + j, ONE + i);
5633             if(message[0] == '+' || message[0] == '~') {
5634               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5635                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5636                         AAA + j, ONE + i);
5637             }
5638             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5639                 message[1] = BOARD_RGHT   - 1 - j + '1';
5640                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5641             }
5642             SendToProgram(message, cps);
5643           }
5644         }
5645       }
5646
5647       SendToProgram(".\n", cps);
5648     }
5649     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5650 }
5651
5652 static int autoQueen; // [HGM] oneclick
5653
5654 int
5655 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5656 {
5657     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5658     /* [HGM] add Shogi promotions */
5659     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5660     ChessSquare piece;
5661     ChessMove moveType;
5662     Boolean premove;
5663
5664     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5665     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5666
5667     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5668       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5669         return FALSE;
5670
5671     piece = boards[currentMove][fromY][fromX];
5672     if(gameInfo.variant == VariantShogi) {
5673         promotionZoneSize = BOARD_HEIGHT/3;
5674         highestPromotingPiece = (int)WhiteFerz;
5675     } else if(gameInfo.variant == VariantMakruk) {
5676         promotionZoneSize = 3;
5677     }
5678
5679     // next weed out all moves that do not touch the promotion zone at all
5680     if((int)piece >= BlackPawn) {
5681         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5682              return FALSE;
5683         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5684     } else {
5685         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5686            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5687     }
5688
5689     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5690
5691     // weed out mandatory Shogi promotions
5692     if(gameInfo.variant == VariantShogi) {
5693         if(piece >= BlackPawn) {
5694             if(toY == 0 && piece == BlackPawn ||
5695                toY == 0 && piece == BlackQueen ||
5696                toY <= 1 && piece == BlackKnight) {
5697                 *promoChoice = '+';
5698                 return FALSE;
5699             }
5700         } else {
5701             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5702                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5703                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5704                 *promoChoice = '+';
5705                 return FALSE;
5706             }
5707         }
5708     }
5709
5710     // weed out obviously illegal Pawn moves
5711     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5712         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5713         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5714         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5715         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5716         // note we are not allowed to test for valid (non-)capture, due to premove
5717     }
5718
5719     // we either have a choice what to promote to, or (in Shogi) whether to promote
5720     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5721         *promoChoice = PieceToChar(BlackFerz);  // no choice
5722         return FALSE;
5723     }
5724     if(autoQueen) { // predetermined
5725         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5726              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5727         else *promoChoice = PieceToChar(BlackQueen);
5728         return FALSE;
5729     }
5730
5731     // suppress promotion popup on illegal moves that are not premoves
5732     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5733               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5734     if(appData.testLegality && !premove) {
5735         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5736                         fromY, fromX, toY, toX, NULLCHAR);
5737         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5738             return FALSE;
5739     }
5740
5741     return TRUE;
5742 }
5743
5744 int
5745 InPalace(row, column)
5746      int row, column;
5747 {   /* [HGM] for Xiangqi */
5748     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5749          column < (BOARD_WIDTH + 4)/2 &&
5750          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5751     return FALSE;
5752 }
5753
5754 int
5755 PieceForSquare (x, y)
5756      int x;
5757      int y;
5758 {
5759   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5760      return -1;
5761   else
5762      return boards[currentMove][y][x];
5763 }
5764
5765 int
5766 OKToStartUserMove(x, y)
5767      int x, y;
5768 {
5769     ChessSquare from_piece;
5770     int white_piece;
5771
5772     if (matchMode) return FALSE;
5773     if (gameMode == EditPosition) return TRUE;
5774
5775     if (x >= 0 && y >= 0)
5776       from_piece = boards[currentMove][y][x];
5777     else
5778       from_piece = EmptySquare;
5779
5780     if (from_piece == EmptySquare) return FALSE;
5781
5782     white_piece = (int)from_piece >= (int)WhitePawn &&
5783       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5784
5785     switch (gameMode) {
5786       case PlayFromGameFile:
5787       case AnalyzeFile:
5788       case TwoMachinesPlay:
5789       case EndOfGame:
5790         return FALSE;
5791
5792       case IcsObserving:
5793       case IcsIdle:
5794         return FALSE;
5795
5796       case MachinePlaysWhite:
5797       case IcsPlayingBlack:
5798         if (appData.zippyPlay) return FALSE;
5799         if (white_piece) {
5800             DisplayMoveError(_("You are playing Black"));
5801             return FALSE;
5802         }
5803         break;
5804
5805       case MachinePlaysBlack:
5806       case IcsPlayingWhite:
5807         if (appData.zippyPlay) return FALSE;
5808         if (!white_piece) {
5809             DisplayMoveError(_("You are playing White"));
5810             return FALSE;
5811         }
5812         break;
5813
5814       case EditGame:
5815         if (!white_piece && WhiteOnMove(currentMove)) {
5816             DisplayMoveError(_("It is White's turn"));
5817             return FALSE;
5818         }
5819         if (white_piece && !WhiteOnMove(currentMove)) {
5820             DisplayMoveError(_("It is Black's turn"));
5821             return FALSE;
5822         }
5823         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5824             /* Editing correspondence game history */
5825             /* Could disallow this or prompt for confirmation */
5826             cmailOldMove = -1;
5827         }
5828         break;
5829
5830       case BeginningOfGame:
5831         if (appData.icsActive) return FALSE;
5832         if (!appData.noChessProgram) {
5833             if (!white_piece) {
5834                 DisplayMoveError(_("You are playing White"));
5835                 return FALSE;
5836             }
5837         }
5838         break;
5839
5840       case Training:
5841         if (!white_piece && WhiteOnMove(currentMove)) {
5842             DisplayMoveError(_("It is White's turn"));
5843             return FALSE;
5844         }
5845         if (white_piece && !WhiteOnMove(currentMove)) {
5846             DisplayMoveError(_("It is Black's turn"));
5847             return FALSE;
5848         }
5849         break;
5850
5851       default:
5852       case IcsExamining:
5853         break;
5854     }
5855     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5856         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5857         && gameMode != AnalyzeFile && gameMode != Training) {
5858         DisplayMoveError(_("Displayed position is not current"));
5859         return FALSE;
5860     }
5861     return TRUE;
5862 }
5863
5864 Boolean
5865 OnlyMove(int *x, int *y, Boolean captures) {
5866     DisambiguateClosure cl;
5867     if (appData.zippyPlay) return FALSE;
5868     switch(gameMode) {
5869       case MachinePlaysBlack:
5870       case IcsPlayingWhite:
5871       case BeginningOfGame:
5872         if(!WhiteOnMove(currentMove)) return FALSE;
5873         break;
5874       case MachinePlaysWhite:
5875       case IcsPlayingBlack:
5876         if(WhiteOnMove(currentMove)) return FALSE;
5877         break;
5878       case EditGame:
5879         break;
5880       default:
5881         return FALSE;
5882     }
5883     cl.pieceIn = EmptySquare;
5884     cl.rfIn = *y;
5885     cl.ffIn = *x;
5886     cl.rtIn = -1;
5887     cl.ftIn = -1;
5888     cl.promoCharIn = NULLCHAR;
5889     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5890     if( cl.kind == NormalMove ||
5891         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5892         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5893         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5894       fromX = cl.ff;
5895       fromY = cl.rf;
5896       *x = cl.ft;
5897       *y = cl.rt;
5898       return TRUE;
5899     }
5900     if(cl.kind != ImpossibleMove) return FALSE;
5901     cl.pieceIn = EmptySquare;
5902     cl.rfIn = -1;
5903     cl.ffIn = -1;
5904     cl.rtIn = *y;
5905     cl.ftIn = *x;
5906     cl.promoCharIn = NULLCHAR;
5907     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5908     if( cl.kind == NormalMove ||
5909         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5910         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5911         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5912       fromX = cl.ff;
5913       fromY = cl.rf;
5914       *x = cl.ft;
5915       *y = cl.rt;
5916       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5917       return TRUE;
5918     }
5919     return FALSE;
5920 }
5921
5922 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5923 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5924 int lastLoadGameUseList = FALSE;
5925 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5926 ChessMove lastLoadGameStart = EndOfFile;
5927
5928 void
5929 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5930      int fromX, fromY, toX, toY;
5931      int promoChar;
5932 {
5933     ChessMove moveType;
5934     ChessSquare pdown, pup;
5935
5936     /* Check if the user is playing in turn.  This is complicated because we
5937        let the user "pick up" a piece before it is his turn.  So the piece he
5938        tried to pick up may have been captured by the time he puts it down!
5939        Therefore we use the color the user is supposed to be playing in this
5940        test, not the color of the piece that is currently on the starting
5941        square---except in EditGame mode, where the user is playing both
5942        sides; fortunately there the capture race can't happen.  (It can
5943        now happen in IcsExamining mode, but that's just too bad.  The user
5944        will get a somewhat confusing message in that case.)
5945        */
5946
5947     switch (gameMode) {
5948       case PlayFromGameFile:
5949       case AnalyzeFile:
5950       case TwoMachinesPlay:
5951       case EndOfGame:
5952       case IcsObserving:
5953       case IcsIdle:
5954         /* We switched into a game mode where moves are not accepted,
5955            perhaps while the mouse button was down. */
5956         return;
5957
5958       case MachinePlaysWhite:
5959         /* User is moving for Black */
5960         if (WhiteOnMove(currentMove)) {
5961             DisplayMoveError(_("It is White's turn"));
5962             return;
5963         }
5964         break;
5965
5966       case MachinePlaysBlack:
5967         /* User is moving for White */
5968         if (!WhiteOnMove(currentMove)) {
5969             DisplayMoveError(_("It is Black's turn"));
5970             return;
5971         }
5972         break;
5973
5974       case EditGame:
5975       case IcsExamining:
5976       case BeginningOfGame:
5977       case AnalyzeMode:
5978       case Training:
5979         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5980             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5981             /* User is moving for Black */
5982             if (WhiteOnMove(currentMove)) {
5983                 DisplayMoveError(_("It is White's turn"));
5984                 return;
5985             }
5986         } else {
5987             /* User is moving for White */
5988             if (!WhiteOnMove(currentMove)) {
5989                 DisplayMoveError(_("It is Black's turn"));
5990                 return;
5991             }
5992         }
5993         break;
5994
5995       case IcsPlayingBlack:
5996         /* User is moving for Black */
5997         if (WhiteOnMove(currentMove)) {
5998             if (!appData.premove) {
5999                 DisplayMoveError(_("It is White's turn"));
6000             } else if (toX >= 0 && toY >= 0) {
6001                 premoveToX = toX;
6002                 premoveToY = toY;
6003                 premoveFromX = fromX;
6004                 premoveFromY = fromY;
6005                 premovePromoChar = promoChar;
6006                 gotPremove = 1;
6007                 if (appData.debugMode)
6008                     fprintf(debugFP, "Got premove: fromX %d,"
6009                             "fromY %d, toX %d, toY %d\n",
6010                             fromX, fromY, toX, toY);
6011             }
6012             return;
6013         }
6014         break;
6015
6016       case IcsPlayingWhite:
6017         /* User is moving for White */
6018         if (!WhiteOnMove(currentMove)) {
6019             if (!appData.premove) {
6020                 DisplayMoveError(_("It is Black's turn"));
6021             } else if (toX >= 0 && toY >= 0) {
6022                 premoveToX = toX;
6023                 premoveToY = toY;
6024                 premoveFromX = fromX;
6025                 premoveFromY = fromY;
6026                 premovePromoChar = promoChar;
6027                 gotPremove = 1;
6028                 if (appData.debugMode)
6029                     fprintf(debugFP, "Got premove: fromX %d,"
6030                             "fromY %d, toX %d, toY %d\n",
6031                             fromX, fromY, toX, toY);
6032             }
6033             return;
6034         }
6035         break;
6036
6037       default:
6038         break;
6039
6040       case EditPosition:
6041         /* EditPosition, empty square, or different color piece;
6042            click-click move is possible */
6043         if (toX == -2 || toY == -2) {
6044             boards[0][fromY][fromX] = EmptySquare;
6045             DrawPosition(FALSE, boards[currentMove]);
6046             return;
6047         } else if (toX >= 0 && toY >= 0) {
6048             boards[0][toY][toX] = boards[0][fromY][fromX];
6049             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6050                 if(boards[0][fromY][0] != EmptySquare) {
6051                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6052                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6053                 }
6054             } else
6055             if(fromX == BOARD_RGHT+1) {
6056                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6057                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6058                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6059                 }
6060             } else
6061             boards[0][fromY][fromX] = EmptySquare;
6062             DrawPosition(FALSE, boards[currentMove]);
6063             return;
6064         }
6065         return;
6066     }
6067
6068     if(toX < 0 || toY < 0) return;
6069     pdown = boards[currentMove][fromY][fromX];
6070     pup = boards[currentMove][toY][toX];
6071
6072     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6073     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
6074          if( pup != EmptySquare ) return;
6075          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6076            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6077                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6078            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6079            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6080            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6081            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6082          fromY = DROP_RANK;
6083     }
6084
6085     /* [HGM] always test for legality, to get promotion info */
6086     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6087                                          fromY, fromX, toY, toX, promoChar);
6088     /* [HGM] but possibly ignore an IllegalMove result */
6089     if (appData.testLegality) {
6090         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6091             DisplayMoveError(_("Illegal move"));
6092             return;
6093         }
6094     }
6095
6096     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6097 }
6098
6099 /* Common tail of UserMoveEvent and DropMenuEvent */
6100 int
6101 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6102      ChessMove moveType;
6103      int fromX, fromY, toX, toY;
6104      /*char*/int promoChar;
6105 {
6106     char *bookHit = 0;
6107
6108     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6109         // [HGM] superchess: suppress promotions to non-available piece
6110         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6111         if(WhiteOnMove(currentMove)) {
6112             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6113         } else {
6114             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6115         }
6116     }
6117
6118     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6119        move type in caller when we know the move is a legal promotion */
6120     if(moveType == NormalMove && promoChar)
6121         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6122
6123     /* [HGM] <popupFix> The following if has been moved here from
6124        UserMoveEvent(). Because it seemed to belong here (why not allow
6125        piece drops in training games?), and because it can only be
6126        performed after it is known to what we promote. */
6127     if (gameMode == Training) {
6128       /* compare the move played on the board to the next move in the
6129        * game. If they match, display the move and the opponent's response.
6130        * If they don't match, display an error message.
6131        */
6132       int saveAnimate;
6133       Board testBoard;
6134       CopyBoard(testBoard, boards[currentMove]);
6135       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6136
6137       if (CompareBoards(testBoard, boards[currentMove+1])) {
6138         ForwardInner(currentMove+1);
6139
6140         /* Autoplay the opponent's response.
6141          * if appData.animate was TRUE when Training mode was entered,
6142          * the response will be animated.
6143          */
6144         saveAnimate = appData.animate;
6145         appData.animate = animateTraining;
6146         ForwardInner(currentMove+1);
6147         appData.animate = saveAnimate;
6148
6149         /* check for the end of the game */
6150         if (currentMove >= forwardMostMove) {
6151           gameMode = PlayFromGameFile;
6152           ModeHighlight();
6153           SetTrainingModeOff();
6154           DisplayInformation(_("End of game"));
6155         }
6156       } else {
6157         DisplayError(_("Incorrect move"), 0);
6158       }
6159       return 1;
6160     }
6161
6162   /* Ok, now we know that the move is good, so we can kill
6163      the previous line in Analysis Mode */
6164   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6165                                 && currentMove < forwardMostMove) {
6166     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6167     else forwardMostMove = currentMove;
6168   }
6169
6170   /* If we need the chess program but it's dead, restart it */
6171   ResurrectChessProgram();
6172
6173   /* A user move restarts a paused game*/
6174   if (pausing)
6175     PauseEvent();
6176
6177   thinkOutput[0] = NULLCHAR;
6178
6179   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6180
6181   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6182
6183   if (gameMode == BeginningOfGame) {
6184     if (appData.noChessProgram) {
6185       gameMode = EditGame;
6186       SetGameInfo();
6187     } else {
6188       char buf[MSG_SIZ];
6189       gameMode = MachinePlaysBlack;
6190       StartClocks();
6191       SetGameInfo();
6192       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6193       DisplayTitle(buf);
6194       if (first.sendName) {
6195         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6196         SendToProgram(buf, &first);
6197       }
6198       StartClocks();
6199     }
6200     ModeHighlight();
6201   }
6202
6203   /* Relay move to ICS or chess engine */
6204   if (appData.icsActive) {
6205     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6206         gameMode == IcsExamining) {
6207       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6208         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6209         SendToICS("draw ");
6210         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6211       }
6212       // also send plain move, in case ICS does not understand atomic claims
6213       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6214       ics_user_moved = 1;
6215     }
6216   } else {
6217     if (first.sendTime && (gameMode == BeginningOfGame ||
6218                            gameMode == MachinePlaysWhite ||
6219                            gameMode == MachinePlaysBlack)) {
6220       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6221     }
6222     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6223          // [HGM] book: if program might be playing, let it use book
6224         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6225         first.maybeThinking = TRUE;
6226     } else SendMoveToProgram(forwardMostMove-1, &first);
6227     if (currentMove == cmailOldMove + 1) {
6228       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6229     }
6230   }
6231
6232   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6233
6234   switch (gameMode) {
6235   case EditGame:
6236     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6237     case MT_NONE:
6238     case MT_CHECK:
6239       break;
6240     case MT_CHECKMATE:
6241     case MT_STAINMATE:
6242       if (WhiteOnMove(currentMove)) {
6243         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6244       } else {
6245         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6246       }
6247       break;
6248     case MT_STALEMATE:
6249       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6250       break;
6251     }
6252     break;
6253
6254   case MachinePlaysBlack:
6255   case MachinePlaysWhite:
6256     /* disable certain menu options while machine is thinking */
6257     SetMachineThinkingEnables();
6258     break;
6259
6260   default:
6261     break;
6262   }
6263
6264   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6265
6266   if(bookHit) { // [HGM] book: simulate book reply
6267         static char bookMove[MSG_SIZ]; // a bit generous?
6268
6269         programStats.nodes = programStats.depth = programStats.time =
6270         programStats.score = programStats.got_only_move = 0;
6271         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6272
6273         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6274         strcat(bookMove, bookHit);
6275         HandleMachineMove(bookMove, &first);
6276   }
6277   return 1;
6278 }
6279
6280 void
6281 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6282      Board board;
6283      int flags;
6284      ChessMove kind;
6285      int rf, ff, rt, ft;
6286      VOIDSTAR closure;
6287 {
6288     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6289     Markers *m = (Markers *) closure;
6290     if(rf == fromY && ff == fromX)
6291         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6292                          || kind == WhiteCapturesEnPassant
6293                          || kind == BlackCapturesEnPassant);
6294     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6295 }
6296
6297 void
6298 MarkTargetSquares(int clear)
6299 {
6300   int x, y;
6301   if(!appData.markers || !appData.highlightDragging ||
6302      !appData.testLegality || gameMode == EditPosition) return;
6303   if(clear) {
6304     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6305   } else {
6306     int capt = 0;
6307     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6308     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6309       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6310       if(capt)
6311       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6312     }
6313   }
6314   DrawPosition(TRUE, NULL);
6315 }
6316
6317 void LeftClick(ClickType clickType, int xPix, int yPix)
6318 {
6319     int x, y;
6320     Boolean saveAnimate;
6321     static int second = 0, promotionChoice = 0, dragging = 0;
6322     char promoChoice = NULLCHAR;
6323
6324     if(appData.seekGraph && appData.icsActive && loggedOn &&
6325         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6326         SeekGraphClick(clickType, xPix, yPix, 0);
6327         return;
6328     }
6329
6330     if (clickType == Press) ErrorPopDown();
6331     MarkTargetSquares(1);
6332
6333     x = EventToSquare(xPix, BOARD_WIDTH);
6334     y = EventToSquare(yPix, BOARD_HEIGHT);
6335     if (!flipView && y >= 0) {
6336         y = BOARD_HEIGHT - 1 - y;
6337     }
6338     if (flipView && x >= 0) {
6339         x = BOARD_WIDTH - 1 - x;
6340     }
6341
6342     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6343         if(clickType == Release) return; // ignore upclick of click-click destination
6344         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6345         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6346         if(gameInfo.holdingsWidth &&
6347                 (WhiteOnMove(currentMove)
6348                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6349                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6350             // click in right holdings, for determining promotion piece
6351             ChessSquare p = boards[currentMove][y][x];
6352             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6353             if(p != EmptySquare) {
6354                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6355                 fromX = fromY = -1;
6356                 return;
6357             }
6358         }
6359         DrawPosition(FALSE, boards[currentMove]);
6360         return;
6361     }
6362
6363     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6364     if(clickType == Press
6365             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6366               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6367               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6368         return;
6369
6370     autoQueen = appData.alwaysPromoteToQueen;
6371
6372     if (fromX == -1) {
6373       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6374         if (clickType == Press) {
6375             /* First square */
6376             if (OKToStartUserMove(x, y)) {
6377                 fromX = x;
6378                 fromY = y;
6379                 second = 0;
6380                 MarkTargetSquares(0);
6381                 DragPieceBegin(xPix, yPix); dragging = 1;
6382                 if (appData.highlightDragging) {
6383                     SetHighlights(x, y, -1, -1);
6384                 }
6385             }
6386         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6387             DragPieceEnd(xPix, yPix); dragging = 0;
6388             DrawPosition(FALSE, NULL);
6389         }
6390         return;
6391       }
6392     }
6393
6394     /* fromX != -1 */
6395     if (clickType == Press && gameMode != EditPosition) {
6396         ChessSquare fromP;
6397         ChessSquare toP;
6398         int frc;
6399
6400         // ignore off-board to clicks
6401         if(y < 0 || x < 0) return;
6402
6403         /* Check if clicking again on the same color piece */
6404         fromP = boards[currentMove][fromY][fromX];
6405         toP = boards[currentMove][y][x];
6406         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6407         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6408              WhitePawn <= toP && toP <= WhiteKing &&
6409              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6410              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6411             (BlackPawn <= fromP && fromP <= BlackKing &&
6412              BlackPawn <= toP && toP <= BlackKing &&
6413              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6414              !(fromP == BlackKing && toP == BlackRook && frc))) {
6415             /* Clicked again on same color piece -- changed his mind */
6416             second = (x == fromX && y == fromY);
6417            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6418             if (appData.highlightDragging) {
6419                 SetHighlights(x, y, -1, -1);
6420             } else {
6421                 ClearHighlights();
6422             }
6423             if (OKToStartUserMove(x, y)) {
6424                 fromX = x;
6425                 fromY = y; dragging = 1;
6426                 MarkTargetSquares(0);
6427                 DragPieceBegin(xPix, yPix);
6428             }
6429             return;
6430            }
6431         }
6432         // ignore clicks on holdings
6433         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6434     }
6435
6436     if (clickType == Release && x == fromX && y == fromY) {
6437         DragPieceEnd(xPix, yPix); dragging = 0;
6438         if (appData.animateDragging) {
6439             /* Undo animation damage if any */
6440             DrawPosition(FALSE, NULL);
6441         }
6442         if (second) {
6443             /* Second up/down in same square; just abort move */
6444             second = 0;
6445             fromX = fromY = -1;
6446             ClearHighlights();
6447             gotPremove = 0;
6448             ClearPremoveHighlights();
6449         } else {
6450             /* First upclick in same square; start click-click mode */
6451             SetHighlights(x, y, -1, -1);
6452         }
6453         return;
6454     }
6455
6456     /* we now have a different from- and (possibly off-board) to-square */
6457     /* Completed move */
6458     toX = x;
6459     toY = y;
6460     saveAnimate = appData.animate;
6461     if (clickType == Press) {
6462         /* Finish clickclick move */
6463         if (appData.animate || appData.highlightLastMove) {
6464             SetHighlights(fromX, fromY, toX, toY);
6465         } else {
6466             ClearHighlights();
6467         }
6468     } else {
6469         /* Finish drag move */
6470         if (appData.highlightLastMove) {
6471             SetHighlights(fromX, fromY, toX, toY);
6472         } else {
6473             ClearHighlights();
6474         }
6475         DragPieceEnd(xPix, yPix); dragging = 0;
6476         /* Don't animate move and drag both */
6477         appData.animate = FALSE;
6478     }
6479
6480     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6481     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6482         ChessSquare piece = boards[currentMove][fromY][fromX];
6483         if(gameMode == EditPosition && piece != EmptySquare &&
6484            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6485             int n;
6486
6487             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6488                 n = PieceToNumber(piece - (int)BlackPawn);
6489                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6490                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6491                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6492             } else
6493             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6494                 n = PieceToNumber(piece);
6495                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6496                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6497                 boards[currentMove][n][BOARD_WIDTH-2]++;
6498             }
6499             boards[currentMove][fromY][fromX] = EmptySquare;
6500         }
6501         ClearHighlights();
6502         fromX = fromY = -1;
6503         DrawPosition(TRUE, boards[currentMove]);
6504         return;
6505     }
6506
6507     // off-board moves should not be highlighted
6508     if(x < 0 || x < 0) ClearHighlights();
6509
6510     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6511         SetHighlights(fromX, fromY, toX, toY);
6512         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6513             // [HGM] super: promotion to captured piece selected from holdings
6514             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6515             promotionChoice = TRUE;
6516             // kludge follows to temporarily execute move on display, without promoting yet
6517             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6518             boards[currentMove][toY][toX] = p;
6519             DrawPosition(FALSE, boards[currentMove]);
6520             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6521             boards[currentMove][toY][toX] = q;
6522             DisplayMessage("Click in holdings to choose piece", "");
6523             return;
6524         }
6525         PromotionPopUp();
6526     } else {
6527         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6528         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6529         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6530         fromX = fromY = -1;
6531     }
6532     appData.animate = saveAnimate;
6533     if (appData.animate || appData.animateDragging) {
6534         /* Undo animation damage if needed */
6535         DrawPosition(FALSE, NULL);
6536     }
6537 }
6538
6539 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6540 {   // front-end-free part taken out of PieceMenuPopup
6541     int whichMenu; int xSqr, ySqr;
6542
6543     if(seekGraphUp) { // [HGM] seekgraph
6544         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6545         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6546         return -2;
6547     }
6548
6549     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6550          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6551         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6552         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6553         if(action == Press)   {
6554             originalFlip = flipView;
6555             flipView = !flipView; // temporarily flip board to see game from partners perspective
6556             DrawPosition(TRUE, partnerBoard);
6557             DisplayMessage(partnerStatus, "");
6558             partnerUp = TRUE;
6559         } else if(action == Release) {
6560             flipView = originalFlip;
6561             DrawPosition(TRUE, boards[currentMove]);
6562             partnerUp = FALSE;
6563         }
6564         return -2;
6565     }
6566
6567     xSqr = EventToSquare(x, BOARD_WIDTH);
6568     ySqr = EventToSquare(y, BOARD_HEIGHT);
6569     if (action == Release) UnLoadPV(); // [HGM] pv
6570     if (action != Press) return -2; // return code to be ignored
6571     switch (gameMode) {
6572       case IcsExamining:
6573         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6574       case EditPosition:
6575         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6576         if (xSqr < 0 || ySqr < 0) return -1;\r
6577         whichMenu = 0; // edit-position menu
6578         break;
6579       case IcsObserving:
6580         if(!appData.icsEngineAnalyze) return -1;
6581       case IcsPlayingWhite:
6582       case IcsPlayingBlack:
6583         if(!appData.zippyPlay) goto noZip;
6584       case AnalyzeMode:
6585       case AnalyzeFile:
6586       case MachinePlaysWhite:
6587       case MachinePlaysBlack:
6588       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6589         if (!appData.dropMenu) {
6590           LoadPV(x, y);
6591           return 2; // flag front-end to grab mouse events
6592         }
6593         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6594            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6595       case EditGame:
6596       noZip:
6597         if (xSqr < 0 || ySqr < 0) return -1;
6598         if (!appData.dropMenu || appData.testLegality &&
6599             gameInfo.variant != VariantBughouse &&
6600             gameInfo.variant != VariantCrazyhouse) return -1;
6601         whichMenu = 1; // drop menu
6602         break;
6603       default:
6604         return -1;
6605     }
6606
6607     if (((*fromX = xSqr) < 0) ||
6608         ((*fromY = ySqr) < 0)) {
6609         *fromX = *fromY = -1;
6610         return -1;
6611     }
6612     if (flipView)
6613       *fromX = BOARD_WIDTH - 1 - *fromX;
6614     else
6615       *fromY = BOARD_HEIGHT - 1 - *fromY;
6616
6617     return whichMenu;
6618 }
6619
6620 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6621 {
6622 //    char * hint = lastHint;
6623     FrontEndProgramStats stats;
6624
6625     stats.which = cps == &first ? 0 : 1;
6626     stats.depth = cpstats->depth;
6627     stats.nodes = cpstats->nodes;
6628     stats.score = cpstats->score;
6629     stats.time = cpstats->time;
6630     stats.pv = cpstats->movelist;
6631     stats.hint = lastHint;
6632     stats.an_move_index = 0;
6633     stats.an_move_count = 0;
6634
6635     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6636         stats.hint = cpstats->move_name;
6637         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6638         stats.an_move_count = cpstats->nr_moves;
6639     }
6640
6641     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
6642
6643     SetProgramStats( &stats );
6644 }
6645
6646 void
6647 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6648 {       // count all piece types
6649         int p, f, r;
6650         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6651         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6652         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6653                 p = board[r][f];
6654                 pCnt[p]++;
6655                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6656                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6657                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6658                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6659                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6660                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6661         }
6662 }
6663
6664 int
6665 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6666 {
6667         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6668         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6669
6670         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6671         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6672         if(myPawns == 2 && nMine == 3) // KPP
6673             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6674         if(myPawns == 1 && nMine == 2) // KP
6675             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6676         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6677             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6678         if(myPawns) return FALSE;
6679         if(pCnt[WhiteRook+side])
6680             return pCnt[BlackRook-side] ||
6681                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6682                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6683                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6684         if(pCnt[WhiteCannon+side]) {
6685             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6686             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6687         }
6688         if(pCnt[WhiteKnight+side])
6689             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6690         return FALSE;
6691 }
6692
6693 int
6694 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6695 {
6696         VariantClass v = gameInfo.variant;
6697
6698         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6699         if(v == VariantShatranj) return TRUE; // always winnable through baring
6700         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6701         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6702
6703         if(v == VariantXiangqi) {
6704                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6705
6706                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6707                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6708                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6709                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6710                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6711                 if(stale) // we have at least one last-rank P plus perhaps C
6712                     return majors // KPKX
6713                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6714                 else // KCA*E*
6715                     return pCnt[WhiteFerz+side] // KCAK
6716                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6717                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6718                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6719
6720         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6721                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6722
6723                 if(nMine == 1) return FALSE; // bare King
6724                 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
6725                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6726                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6727                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6728                 if(pCnt[WhiteKnight+side])
6729                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6730                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6731                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6732                 if(nBishops)
6733                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6734                 if(pCnt[WhiteAlfil+side])
6735                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6736                 if(pCnt[WhiteWazir+side])
6737                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6738         }
6739
6740         return TRUE;
6741 }
6742
6743 int
6744 Adjudicate(ChessProgramState *cps)
6745 {       // [HGM] some adjudications useful with buggy engines
6746         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6747         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6748         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6749         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6750         int k, count = 0; static int bare = 1;
6751         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6752         Boolean canAdjudicate = !appData.icsActive;
6753
6754         // most tests only when we understand the game, i.e. legality-checking on
6755             if( appData.testLegality )
6756             {   /* [HGM] Some more adjudications for obstinate engines */
6757                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6758                 static int moveCount = 6;
6759                 ChessMove result;
6760                 char *reason = NULL;
6761
6762                 /* Count what is on board. */
6763                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6764
6765                 /* Some material-based adjudications that have to be made before stalemate test */
6766                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6767                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6768                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6769                      if(canAdjudicate && appData.checkMates) {
6770                          if(engineOpponent)
6771                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6772                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6773                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6774                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6775                          return 1;
6776                      }
6777                 }
6778
6779                 /* Bare King in Shatranj (loses) or Losers (wins) */
6780                 if( nrW == 1 || nrB == 1) {
6781                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6782                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6783                      if(canAdjudicate && appData.checkMates) {
6784                          if(engineOpponent)
6785                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6786                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6787                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6788                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6789                          return 1;
6790                      }
6791                   } else
6792                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6793                   {    /* bare King */
6794                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6795                         if(canAdjudicate && appData.checkMates) {
6796                             /* but only adjudicate if adjudication enabled */
6797                             if(engineOpponent)
6798                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6799                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6800                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6801                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6802                             return 1;
6803                         }
6804                   }
6805                 } else bare = 1;
6806
6807
6808             // don't wait for engine to announce game end if we can judge ourselves
6809             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6810               case MT_CHECK:
6811                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6812                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6813                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6814                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6815                             checkCnt++;
6816                         if(checkCnt >= 2) {
6817                             reason = "Xboard adjudication: 3rd check";
6818                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6819                             break;
6820                         }
6821                     }
6822                 }
6823               case MT_NONE:
6824               default:
6825                 break;
6826               case MT_STALEMATE:
6827               case MT_STAINMATE:
6828                 reason = "Xboard adjudication: Stalemate";
6829                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6830                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6831                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6832                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6833                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6834                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6835                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6836                                                                         EP_CHECKMATE : EP_WINS);
6837                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6838                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6839                 }
6840                 break;
6841               case MT_CHECKMATE:
6842                 reason = "Xboard adjudication: Checkmate";
6843                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6844                 break;
6845             }
6846
6847                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6848                     case EP_STALEMATE:
6849                         result = GameIsDrawn; break;
6850                     case EP_CHECKMATE:
6851                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6852                     case EP_WINS:
6853                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6854                     default:
6855                         result = EndOfFile;
6856                 }
6857                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6858                     if(engineOpponent)
6859                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6860                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6861                     GameEnds( result, reason, GE_XBOARD );
6862                     return 1;
6863                 }
6864
6865                 /* Next absolutely insufficient mating material. */
6866                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6867                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6868                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6869
6870                      /* always flag draws, for judging claims */
6871                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6872
6873                      if(canAdjudicate && appData.materialDraws) {
6874                          /* but only adjudicate them if adjudication enabled */
6875                          if(engineOpponent) {
6876                            SendToProgram("force\n", engineOpponent); // suppress reply
6877                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6878                          }
6879                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6880                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6881                          return 1;
6882                      }
6883                 }
6884
6885                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6886                 if(gameInfo.variant == VariantXiangqi ?
6887                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6888                  : nrW + nrB == 4 &&
6889                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6890                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6891                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6892                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6893                    ) ) {
6894                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6895                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6896                           if(engineOpponent) {
6897                             SendToProgram("force\n", engineOpponent); // suppress reply
6898                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6899                           }
6900                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6901                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6902                           return 1;
6903                      }
6904                 } else moveCount = 6;
6905             }
6906         if (appData.debugMode) { int i;
6907             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6908                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6909                     appData.drawRepeats);
6910             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6911               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6912
6913         }
6914
6915         // Repetition draws and 50-move rule can be applied independently of legality testing
6916
6917                 /* Check for rep-draws */
6918                 count = 0;
6919                 for(k = forwardMostMove-2;
6920                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6921                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6922                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6923                     k-=2)
6924                 {   int rights=0;
6925                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6926                         /* compare castling rights */
6927                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6928                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6929                                 rights++; /* King lost rights, while rook still had them */
6930                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6931                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6932                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6933                                    rights++; /* but at least one rook lost them */
6934                         }
6935                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6936                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6937                                 rights++;
6938                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6939                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6940                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6941                                    rights++;
6942                         }
6943                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6944                             && appData.drawRepeats > 1) {
6945                              /* adjudicate after user-specified nr of repeats */
6946                              int result = GameIsDrawn;
6947                              char *details = "XBoard adjudication: repetition draw";
6948                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6949                                 // [HGM] xiangqi: check for forbidden perpetuals
6950                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6951                                 for(m=forwardMostMove; m>k; m-=2) {
6952                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6953                                         ourPerpetual = 0; // the current mover did not always check
6954                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6955                                         hisPerpetual = 0; // the opponent did not always check
6956                                 }
6957                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6958                                                                         ourPerpetual, hisPerpetual);
6959                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6960                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6961                                     details = "Xboard adjudication: perpetual checking";
6962                                 } else
6963                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6964                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6965                                 } else
6966                                 // Now check for perpetual chases
6967                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6968                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6969                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6970                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6971                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6972                                         details = "Xboard adjudication: perpetual chasing";
6973                                     } else
6974                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6975                                         break; // Abort repetition-checking loop.
6976                                 }
6977                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6978                              }
6979                              if(engineOpponent) {
6980                                SendToProgram("force\n", engineOpponent); // suppress reply
6981                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6982                              }
6983                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6984                              GameEnds( result, details, GE_XBOARD );
6985                              return 1;
6986                         }
6987                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6988                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6989                     }
6990                 }
6991
6992                 /* Now we test for 50-move draws. Determine ply count */
6993                 count = forwardMostMove;
6994                 /* look for last irreversble move */
6995                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6996                     count--;
6997                 /* if we hit starting position, add initial plies */
6998                 if( count == backwardMostMove )
6999                     count -= initialRulePlies;
7000                 count = forwardMostMove - count;
7001                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7002                         // adjust reversible move counter for checks in Xiangqi
7003                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7004                         if(i < backwardMostMove) i = backwardMostMove;
7005                         while(i <= forwardMostMove) {
7006                                 lastCheck = inCheck; // check evasion does not count
7007                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7008                                 if(inCheck || lastCheck) count--; // check does not count
7009                                 i++;
7010                         }
7011                 }
7012                 if( count >= 100)
7013                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7014                          /* this is used to judge if draw claims are legal */
7015                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7016                          if(engineOpponent) {
7017                            SendToProgram("force\n", engineOpponent); // suppress reply
7018                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7019                          }
7020                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7021                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7022                          return 1;
7023                 }
7024
7025                 /* if draw offer is pending, treat it as a draw claim
7026                  * when draw condition present, to allow engines a way to
7027                  * claim draws before making their move to avoid a race
7028                  * condition occurring after their move
7029                  */
7030                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7031                          char *p = NULL;
7032                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7033                              p = "Draw claim: 50-move rule";
7034                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7035                              p = "Draw claim: 3-fold repetition";
7036                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7037                              p = "Draw claim: insufficient mating material";
7038                          if( p != NULL && canAdjudicate) {
7039                              if(engineOpponent) {
7040                                SendToProgram("force\n", engineOpponent); // suppress reply
7041                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7042                              }
7043                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7044                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7045                              return 1;
7046                          }
7047                 }
7048
7049                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7050                     if(engineOpponent) {
7051                       SendToProgram("force\n", engineOpponent); // suppress reply
7052                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7053                     }
7054                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7055                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7056                     return 1;
7057                 }
7058         return 0;
7059 }
7060
7061 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7062 {   // [HGM] book: this routine intercepts moves to simulate book replies
7063     char *bookHit = NULL;
7064
7065     //first determine if the incoming move brings opponent into his book
7066     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7067         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7068     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7069     if(bookHit != NULL && !cps->bookSuspend) {
7070         // make sure opponent is not going to reply after receiving move to book position
7071         SendToProgram("force\n", cps);
7072         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7073     }
7074     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7075     // now arrange restart after book miss
7076     if(bookHit) {
7077         // after a book hit we never send 'go', and the code after the call to this routine
7078         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7079         char buf[MSG_SIZ];
7080         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7081         SendToProgram(buf, cps);
7082         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7083     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7084         SendToProgram("go\n", cps);
7085         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7086     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7087         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7088             SendToProgram("go\n", cps);
7089         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7090     }
7091     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7092 }
7093
7094 char *savedMessage;
7095 ChessProgramState *savedState;
7096 void DeferredBookMove(void)
7097 {
7098         if(savedState->lastPing != savedState->lastPong)
7099                     ScheduleDelayedEvent(DeferredBookMove, 10);
7100         else
7101         HandleMachineMove(savedMessage, savedState);
7102 }
7103
7104 void
7105 HandleMachineMove(message, cps)
7106      char *message;
7107      ChessProgramState *cps;
7108 {
7109     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7110     char realname[MSG_SIZ];
7111     int fromX, fromY, toX, toY;
7112     ChessMove moveType;
7113     char promoChar;
7114     char *p;
7115     int machineWhite;
7116     char *bookHit;
7117
7118     cps->userError = 0;
7119
7120 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7121     /*
7122      * Kludge to ignore BEL characters
7123      */
7124     while (*message == '\007') message++;
7125
7126     /*
7127      * [HGM] engine debug message: ignore lines starting with '#' character
7128      */
7129     if(cps->debug && *message == '#') return;
7130
7131     /*
7132      * Look for book output
7133      */
7134     if (cps == &first && bookRequested) {
7135         if (message[0] == '\t' || message[0] == ' ') {
7136             /* Part of the book output is here; append it */
7137             strcat(bookOutput, message);
7138             strcat(bookOutput, "  \n");
7139             return;
7140         } else if (bookOutput[0] != NULLCHAR) {
7141             /* All of book output has arrived; display it */
7142             char *p = bookOutput;
7143             while (*p != NULLCHAR) {
7144                 if (*p == '\t') *p = ' ';
7145                 p++;
7146             }
7147             DisplayInformation(bookOutput);
7148             bookRequested = FALSE;
7149             /* Fall through to parse the current output */
7150         }
7151     }
7152
7153     /*
7154      * Look for machine move.
7155      */
7156     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7157         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7158     {
7159         /* This method is only useful on engines that support ping */
7160         if (cps->lastPing != cps->lastPong) {
7161           if (gameMode == BeginningOfGame) {
7162             /* Extra move from before last new; ignore */
7163             if (appData.debugMode) {
7164                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7165             }
7166           } else {
7167             if (appData.debugMode) {
7168                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7169                         cps->which, gameMode);
7170             }
7171
7172             SendToProgram("undo\n", cps);
7173           }
7174           return;
7175         }
7176
7177         switch (gameMode) {
7178           case BeginningOfGame:
7179             /* Extra move from before last reset; ignore */
7180             if (appData.debugMode) {
7181                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7182             }
7183             return;
7184
7185           case EndOfGame:
7186           case IcsIdle:
7187           default:
7188             /* Extra move after we tried to stop.  The mode test is
7189                not a reliable way of detecting this problem, but it's
7190                the best we can do on engines that don't support ping.
7191             */
7192             if (appData.debugMode) {
7193                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7194                         cps->which, gameMode);
7195             }
7196             SendToProgram("undo\n", cps);
7197             return;
7198
7199           case MachinePlaysWhite:
7200           case IcsPlayingWhite:
7201             machineWhite = TRUE;
7202             break;
7203
7204           case MachinePlaysBlack:
7205           case IcsPlayingBlack:
7206             machineWhite = FALSE;
7207             break;
7208
7209           case TwoMachinesPlay:
7210             machineWhite = (cps->twoMachinesColor[0] == 'w');
7211             break;
7212         }
7213         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7214             if (appData.debugMode) {
7215                 fprintf(debugFP,
7216                         "Ignoring move out of turn by %s, gameMode %d"
7217                         ", forwardMost %d\n",
7218                         cps->which, gameMode, forwardMostMove);
7219             }
7220             return;
7221         }
7222
7223     if (appData.debugMode) { int f = forwardMostMove;
7224         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7225                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7226                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7227     }
7228         if(cps->alphaRank) AlphaRank(machineMove, 4);
7229         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7230                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7231             /* Machine move could not be parsed; ignore it. */
7232           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7233                     machineMove, cps->which);
7234             DisplayError(buf1, 0);
7235             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7236                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7237             if (gameMode == TwoMachinesPlay) {
7238               GameEnds(machineWhite ? BlackWins : WhiteWins,
7239                        buf1, GE_XBOARD);
7240             }
7241             return;
7242         }
7243
7244         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7245         /* So we have to redo legality test with true e.p. status here,  */
7246         /* to make sure an illegal e.p. capture does not slip through,   */
7247         /* to cause a forfeit on a justified illegal-move complaint      */
7248         /* of the opponent.                                              */
7249         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7250            ChessMove moveType;
7251            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7252                              fromY, fromX, toY, toX, promoChar);
7253             if (appData.debugMode) {
7254                 int i;
7255                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7256                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7257                 fprintf(debugFP, "castling rights\n");
7258             }
7259             if(moveType == IllegalMove) {
7260               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7261                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7262                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7263                            buf1, GE_XBOARD);
7264                 return;
7265            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7266            /* [HGM] Kludge to handle engines that send FRC-style castling
7267               when they shouldn't (like TSCP-Gothic) */
7268            switch(moveType) {
7269              case WhiteASideCastleFR:
7270              case BlackASideCastleFR:
7271                toX+=2;
7272                currentMoveString[2]++;
7273                break;
7274              case WhiteHSideCastleFR:
7275              case BlackHSideCastleFR:
7276                toX--;
7277                currentMoveString[2]--;
7278                break;
7279              default: ; // nothing to do, but suppresses warning of pedantic compilers
7280            }
7281         }
7282         hintRequested = FALSE;
7283         lastHint[0] = NULLCHAR;
7284         bookRequested = FALSE;
7285         /* Program may be pondering now */
7286         cps->maybeThinking = TRUE;
7287         if (cps->sendTime == 2) cps->sendTime = 1;
7288         if (cps->offeredDraw) cps->offeredDraw--;
7289
7290         /* currentMoveString is set as a side-effect of ParseOneMove */
7291         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7292         strcat(machineMove, "\n");
7293         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7294
7295         /* [AS] Save move info*/
7296         pvInfoList[ forwardMostMove ].score = programStats.score;
7297         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7298         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7299
7300         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7301
7302         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7303         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7304             int count = 0;
7305
7306             while( count < adjudicateLossPlies ) {
7307                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7308
7309                 if( count & 1 ) {
7310                     score = -score; /* Flip score for winning side */
7311                 }
7312
7313                 if( score > adjudicateLossThreshold ) {
7314                     break;
7315                 }
7316
7317                 count++;
7318             }
7319
7320             if( count >= adjudicateLossPlies ) {
7321                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7322
7323                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7324                     "Xboard adjudication",
7325                     GE_XBOARD );
7326
7327                 return;
7328             }
7329         }
7330
7331         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7332
7333 #if ZIPPY
7334         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7335             first.initDone) {
7336           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7337                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7338                 SendToICS("draw ");
7339                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7340           }
7341           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7342           ics_user_moved = 1;
7343           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7344                 char buf[3*MSG_SIZ];
7345
7346                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7347                         programStats.score / 100.,
7348                         programStats.depth,
7349                         programStats.time / 100.,
7350                         (unsigned int)programStats.nodes,
7351                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7352                         programStats.movelist);
7353                 SendToICS(buf);
7354 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7355           }
7356         }
7357 #endif
7358
7359         /* [AS] Clear stats for next move */
7360         ClearProgramStats();
7361         thinkOutput[0] = NULLCHAR;
7362         hiddenThinkOutputState = 0;
7363
7364         bookHit = NULL;
7365         if (gameMode == TwoMachinesPlay) {
7366             /* [HGM] relaying draw offers moved to after reception of move */
7367             /* and interpreting offer as claim if it brings draw condition */
7368             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7369                 SendToProgram("draw\n", cps->other);
7370             }
7371             if (cps->other->sendTime) {
7372                 SendTimeRemaining(cps->other,
7373                                   cps->other->twoMachinesColor[0] == 'w');
7374             }
7375             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7376             if (firstMove && !bookHit) {
7377                 firstMove = FALSE;
7378                 if (cps->other->useColors) {
7379                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7380                 }
7381                 SendToProgram("go\n", cps->other);
7382             }
7383             cps->other->maybeThinking = TRUE;
7384         }
7385
7386         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7387
7388         if (!pausing && appData.ringBellAfterMoves) {
7389             RingBell();
7390         }
7391
7392         /*
7393          * Reenable menu items that were disabled while
7394          * machine was thinking
7395          */
7396         if (gameMode != TwoMachinesPlay)
7397             SetUserThinkingEnables();
7398
7399         // [HGM] book: after book hit opponent has received move and is now in force mode
7400         // force the book reply into it, and then fake that it outputted this move by jumping
7401         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7402         if(bookHit) {
7403                 static char bookMove[MSG_SIZ]; // a bit generous?
7404
7405                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7406                 strcat(bookMove, bookHit);
7407                 message = bookMove;
7408                 cps = cps->other;
7409                 programStats.nodes = programStats.depth = programStats.time =
7410                 programStats.score = programStats.got_only_move = 0;
7411                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7412
7413                 if(cps->lastPing != cps->lastPong) {
7414                     savedMessage = message; // args for deferred call
7415                     savedState = cps;
7416                     ScheduleDelayedEvent(DeferredBookMove, 10);
7417                     return;
7418                 }
7419                 goto FakeBookMove;
7420         }
7421
7422         return;
7423     }
7424
7425     /* Set special modes for chess engines.  Later something general
7426      *  could be added here; for now there is just one kludge feature,
7427      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7428      *  when "xboard" is given as an interactive command.
7429      */
7430     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7431         cps->useSigint = FALSE;
7432         cps->useSigterm = FALSE;
7433     }
7434     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7435       ParseFeatures(message+8, cps);
7436       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7437     }
7438
7439     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7440      * want this, I was asked to put it in, and obliged.
7441      */
7442     if (!strncmp(message, "setboard ", 9)) {
7443         Board initial_position;
7444
7445         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7446
7447         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7448             DisplayError(_("Bad FEN received from engine"), 0);
7449             return ;
7450         } else {
7451            Reset(TRUE, FALSE);
7452            CopyBoard(boards[0], initial_position);
7453            initialRulePlies = FENrulePlies;
7454            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7455            else gameMode = MachinePlaysBlack;
7456            DrawPosition(FALSE, boards[currentMove]);
7457         }
7458         return;
7459     }
7460
7461     /*
7462      * Look for communication commands
7463      */
7464     if (!strncmp(message, "telluser ", 9)) {
7465         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7466         DisplayNote(message + 9);
7467         return;
7468     }
7469     if (!strncmp(message, "tellusererror ", 14)) {
7470         cps->userError = 1;
7471         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7472         DisplayError(message + 14, 0);
7473         return;
7474     }
7475     if (!strncmp(message, "tellopponent ", 13)) {
7476       if (appData.icsActive) {
7477         if (loggedOn) {
7478           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7479           SendToICS(buf1);
7480         }
7481       } else {
7482         DisplayNote(message + 13);
7483       }
7484       return;
7485     }
7486     if (!strncmp(message, "tellothers ", 11)) {
7487       if (appData.icsActive) {
7488         if (loggedOn) {
7489           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7490           SendToICS(buf1);
7491         }
7492       }
7493       return;
7494     }
7495     if (!strncmp(message, "tellall ", 8)) {
7496       if (appData.icsActive) {
7497         if (loggedOn) {
7498           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7499           SendToICS(buf1);
7500         }
7501       } else {
7502         DisplayNote(message + 8);
7503       }
7504       return;
7505     }
7506     if (strncmp(message, "warning", 7) == 0) {
7507         /* Undocumented feature, use tellusererror in new code */
7508         DisplayError(message, 0);
7509         return;
7510     }
7511     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7512         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7513         strcat(realname, " query");
7514         AskQuestion(realname, buf2, buf1, cps->pr);
7515         return;
7516     }
7517     /* Commands from the engine directly to ICS.  We don't allow these to be
7518      *  sent until we are logged on. Crafty kibitzes have been known to
7519      *  interfere with the login process.
7520      */
7521     if (loggedOn) {
7522         if (!strncmp(message, "tellics ", 8)) {
7523             SendToICS(message + 8);
7524             SendToICS("\n");
7525             return;
7526         }
7527         if (!strncmp(message, "tellicsnoalias ", 15)) {
7528             SendToICS(ics_prefix);
7529             SendToICS(message + 15);
7530             SendToICS("\n");
7531             return;
7532         }
7533         /* The following are for backward compatibility only */
7534         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7535             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7536             SendToICS(ics_prefix);
7537             SendToICS(message);
7538             SendToICS("\n");
7539             return;
7540         }
7541     }
7542     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7543         return;
7544     }
7545     /*
7546      * If the move is illegal, cancel it and redraw the board.
7547      * Also deal with other error cases.  Matching is rather loose
7548      * here to accommodate engines written before the spec.
7549      */
7550     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7551         strncmp(message, "Error", 5) == 0) {
7552         if (StrStr(message, "name") ||
7553             StrStr(message, "rating") || StrStr(message, "?") ||
7554             StrStr(message, "result") || StrStr(message, "board") ||
7555             StrStr(message, "bk") || StrStr(message, "computer") ||
7556             StrStr(message, "variant") || StrStr(message, "hint") ||
7557             StrStr(message, "random") || StrStr(message, "depth") ||
7558             StrStr(message, "accepted")) {
7559             return;
7560         }
7561         if (StrStr(message, "protover")) {
7562           /* Program is responding to input, so it's apparently done
7563              initializing, and this error message indicates it is
7564              protocol version 1.  So we don't need to wait any longer
7565              for it to initialize and send feature commands. */
7566           FeatureDone(cps, 1);
7567           cps->protocolVersion = 1;
7568           return;
7569         }
7570         cps->maybeThinking = FALSE;
7571
7572         if (StrStr(message, "draw")) {
7573             /* Program doesn't have "draw" command */
7574             cps->sendDrawOffers = 0;
7575             return;
7576         }
7577         if (cps->sendTime != 1 &&
7578             (StrStr(message, "time") || StrStr(message, "otim"))) {
7579           /* Program apparently doesn't have "time" or "otim" command */
7580           cps->sendTime = 0;
7581           return;
7582         }
7583         if (StrStr(message, "analyze")) {
7584             cps->analysisSupport = FALSE;
7585             cps->analyzing = FALSE;
7586             Reset(FALSE, TRUE);
7587             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7588             DisplayError(buf2, 0);
7589             return;
7590         }
7591         if (StrStr(message, "(no matching move)st")) {
7592           /* Special kludge for GNU Chess 4 only */
7593           cps->stKludge = TRUE;
7594           SendTimeControl(cps, movesPerSession, timeControl,
7595                           timeIncrement, appData.searchDepth,
7596                           searchTime);
7597           return;
7598         }
7599         if (StrStr(message, "(no matching move)sd")) {
7600           /* Special kludge for GNU Chess 4 only */
7601           cps->sdKludge = TRUE;
7602           SendTimeControl(cps, movesPerSession, timeControl,
7603                           timeIncrement, appData.searchDepth,
7604                           searchTime);
7605           return;
7606         }
7607         if (!StrStr(message, "llegal")) {
7608             return;
7609         }
7610         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7611             gameMode == IcsIdle) return;
7612         if (forwardMostMove <= backwardMostMove) return;
7613         if (pausing) PauseEvent();
7614       if(appData.forceIllegal) {
7615             // [HGM] illegal: machine refused move; force position after move into it
7616           SendToProgram("force\n", cps);
7617           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7618                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7619                 // when black is to move, while there might be nothing on a2 or black
7620                 // might already have the move. So send the board as if white has the move.
7621                 // But first we must change the stm of the engine, as it refused the last move
7622                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7623                 if(WhiteOnMove(forwardMostMove)) {
7624                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7625                     SendBoard(cps, forwardMostMove); // kludgeless board
7626                 } else {
7627                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7628                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7629                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7630                 }
7631           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7632             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7633                  gameMode == TwoMachinesPlay)
7634               SendToProgram("go\n", cps);
7635             return;
7636       } else
7637         if (gameMode == PlayFromGameFile) {
7638             /* Stop reading this game file */
7639             gameMode = EditGame;
7640             ModeHighlight();
7641         }
7642         currentMove = forwardMostMove-1;
7643         DisplayMove(currentMove-1); /* before DisplayMoveError */
7644         SwitchClocks(forwardMostMove-1); // [HGM] race
7645         DisplayBothClocks();
7646         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7647                 parseList[currentMove], cps->which);
7648         DisplayMoveError(buf1);
7649         DrawPosition(FALSE, boards[currentMove]);
7650
7651         /* [HGM] illegal-move claim should forfeit game when Xboard */
7652         /* only passes fully legal moves                            */
7653         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7654             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7655                                 "False illegal-move claim", GE_XBOARD );
7656         }
7657         return;
7658     }
7659     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7660         /* Program has a broken "time" command that
7661            outputs a string not ending in newline.
7662            Don't use it. */
7663         cps->sendTime = 0;
7664     }
7665
7666     /*
7667      * If chess program startup fails, exit with an error message.
7668      * Attempts to recover here are futile.
7669      */
7670     if ((StrStr(message, "unknown host") != NULL)
7671         || (StrStr(message, "No remote directory") != NULL)
7672         || (StrStr(message, "not found") != NULL)
7673         || (StrStr(message, "No such file") != NULL)
7674         || (StrStr(message, "can't alloc") != NULL)
7675         || (StrStr(message, "Permission denied") != NULL)) {
7676
7677         cps->maybeThinking = FALSE;
7678         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7679                 cps->which, cps->program, cps->host, message);
7680         RemoveInputSource(cps->isr);
7681         DisplayFatalError(buf1, 0, 1);
7682         return;
7683     }
7684
7685     /*
7686      * Look for hint output
7687      */
7688     if (sscanf(message, "Hint: %s", buf1) == 1) {
7689         if (cps == &first && hintRequested) {
7690             hintRequested = FALSE;
7691             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7692                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7693                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7694                                     PosFlags(forwardMostMove),
7695                                     fromY, fromX, toY, toX, promoChar, buf1);
7696                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7697                 DisplayInformation(buf2);
7698             } else {
7699                 /* Hint move could not be parsed!? */
7700               snprintf(buf2, sizeof(buf2),
7701                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7702                         buf1, cps->which);
7703                 DisplayError(buf2, 0);
7704             }
7705         } else {
7706           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7707         }
7708         return;
7709     }
7710
7711     /*
7712      * Ignore other messages if game is not in progress
7713      */
7714     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7715         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7716
7717     /*
7718      * look for win, lose, draw, or draw offer
7719      */
7720     if (strncmp(message, "1-0", 3) == 0) {
7721         char *p, *q, *r = "";
7722         p = strchr(message, '{');
7723         if (p) {
7724             q = strchr(p, '}');
7725             if (q) {
7726                 *q = NULLCHAR;
7727                 r = p + 1;
7728             }
7729         }
7730         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7731         return;
7732     } else if (strncmp(message, "0-1", 3) == 0) {
7733         char *p, *q, *r = "";
7734         p = strchr(message, '{');
7735         if (p) {
7736             q = strchr(p, '}');
7737             if (q) {
7738                 *q = NULLCHAR;
7739                 r = p + 1;
7740             }
7741         }
7742         /* Kludge for Arasan 4.1 bug */
7743         if (strcmp(r, "Black resigns") == 0) {
7744             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7745             return;
7746         }
7747         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7748         return;
7749     } else if (strncmp(message, "1/2", 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
7760         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7761         return;
7762
7763     } else if (strncmp(message, "White resign", 12) == 0) {
7764         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7765         return;
7766     } else if (strncmp(message, "Black resign", 12) == 0) {
7767         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7768         return;
7769     } else if (strncmp(message, "White matches", 13) == 0 ||
7770                strncmp(message, "Black matches", 13) == 0   ) {
7771         /* [HGM] ignore GNUShogi noises */
7772         return;
7773     } else if (strncmp(message, "White", 5) == 0 &&
7774                message[5] != '(' &&
7775                StrStr(message, "Black") == NULL) {
7776         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7777         return;
7778     } else if (strncmp(message, "Black", 5) == 0 &&
7779                message[5] != '(') {
7780         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7781         return;
7782     } else if (strcmp(message, "resign") == 0 ||
7783                strcmp(message, "computer resigns") == 0) {
7784         switch (gameMode) {
7785           case MachinePlaysBlack:
7786           case IcsPlayingBlack:
7787             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7788             break;
7789           case MachinePlaysWhite:
7790           case IcsPlayingWhite:
7791             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7792             break;
7793           case TwoMachinesPlay:
7794             if (cps->twoMachinesColor[0] == 'w')
7795               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7796             else
7797               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7798             break;
7799           default:
7800             /* can't happen */
7801             break;
7802         }
7803         return;
7804     } else if (strncmp(message, "opponent mates", 14) == 0) {
7805         switch (gameMode) {
7806           case MachinePlaysBlack:
7807           case IcsPlayingBlack:
7808             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7809             break;
7810           case MachinePlaysWhite:
7811           case IcsPlayingWhite:
7812             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7813             break;
7814           case TwoMachinesPlay:
7815             if (cps->twoMachinesColor[0] == 'w')
7816               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7817             else
7818               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7819             break;
7820           default:
7821             /* can't happen */
7822             break;
7823         }
7824         return;
7825     } else if (strncmp(message, "computer mates", 14) == 0) {
7826         switch (gameMode) {
7827           case MachinePlaysBlack:
7828           case IcsPlayingBlack:
7829             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7830             break;
7831           case MachinePlaysWhite:
7832           case IcsPlayingWhite:
7833             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7834             break;
7835           case TwoMachinesPlay:
7836             if (cps->twoMachinesColor[0] == 'w')
7837               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7838             else
7839               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7840             break;
7841           default:
7842             /* can't happen */
7843             break;
7844         }
7845         return;
7846     } else if (strncmp(message, "checkmate", 9) == 0) {
7847         if (WhiteOnMove(forwardMostMove)) {
7848             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7849         } else {
7850             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7851         }
7852         return;
7853     } else if (strstr(message, "Draw") != NULL ||
7854                strstr(message, "game is a draw") != NULL) {
7855         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7856         return;
7857     } else if (strstr(message, "offer") != NULL &&
7858                strstr(message, "draw") != NULL) {
7859 #if ZIPPY
7860         if (appData.zippyPlay && first.initDone) {
7861             /* Relay offer to ICS */
7862             SendToICS(ics_prefix);
7863             SendToICS("draw\n");
7864         }
7865 #endif
7866         cps->offeredDraw = 2; /* valid until this engine moves twice */
7867         if (gameMode == TwoMachinesPlay) {
7868             if (cps->other->offeredDraw) {
7869                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7870             /* [HGM] in two-machine mode we delay relaying draw offer      */
7871             /* until after we also have move, to see if it is really claim */
7872             }
7873         } else if (gameMode == MachinePlaysWhite ||
7874                    gameMode == MachinePlaysBlack) {
7875           if (userOfferedDraw) {
7876             DisplayInformation(_("Machine accepts your draw offer"));
7877             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7878           } else {
7879             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7880           }
7881         }
7882     }
7883
7884
7885     /*
7886      * Look for thinking output
7887      */
7888     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7889           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7890                                 ) {
7891         int plylev, mvleft, mvtot, curscore, time;
7892         char mvname[MOVE_LEN];
7893         u64 nodes; // [DM]
7894         char plyext;
7895         int ignore = FALSE;
7896         int prefixHint = FALSE;
7897         mvname[0] = NULLCHAR;
7898
7899         switch (gameMode) {
7900           case MachinePlaysBlack:
7901           case IcsPlayingBlack:
7902             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7903             break;
7904           case MachinePlaysWhite:
7905           case IcsPlayingWhite:
7906             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7907             break;
7908           case AnalyzeMode:
7909           case AnalyzeFile:
7910             break;
7911           case IcsObserving: /* [DM] icsEngineAnalyze */
7912             if (!appData.icsEngineAnalyze) ignore = TRUE;
7913             break;
7914           case TwoMachinesPlay:
7915             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7916                 ignore = TRUE;
7917             }
7918             break;
7919           default:
7920             ignore = TRUE;
7921             break;
7922         }
7923
7924         if (!ignore) {
7925             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7926             buf1[0] = NULLCHAR;
7927             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7928                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7929
7930                 if (plyext != ' ' && plyext != '\t') {
7931                     time *= 100;
7932                 }
7933
7934                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7935                 if( cps->scoreIsAbsolute &&
7936                     ( gameMode == MachinePlaysBlack ||
7937                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7938                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7939                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7940                      !WhiteOnMove(currentMove)
7941                     ) )
7942                 {
7943                     curscore = -curscore;
7944                 }
7945
7946
7947                 tempStats.depth = plylev;
7948                 tempStats.nodes = nodes;
7949                 tempStats.time = time;
7950                 tempStats.score = curscore;
7951                 tempStats.got_only_move = 0;
7952
7953                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7954                         int ticklen;
7955
7956                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7957                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7958                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7959                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7960                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7961                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7962                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7963                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7964                 }
7965
7966                 /* Buffer overflow protection */
7967                 if (buf1[0] != NULLCHAR) {
7968                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7969                         && appData.debugMode) {
7970                         fprintf(debugFP,
7971                                 "PV is too long; using the first %u bytes.\n",
7972                                 (unsigned) sizeof(tempStats.movelist) - 1);
7973                     }
7974
7975                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
7976                 } else {
7977                     sprintf(tempStats.movelist, " no PV\n");
7978                 }
7979
7980                 if (tempStats.seen_stat) {
7981                     tempStats.ok_to_send = 1;
7982                 }
7983
7984                 if (strchr(tempStats.movelist, '(') != NULL) {
7985                     tempStats.line_is_book = 1;
7986                     tempStats.nr_moves = 0;
7987                     tempStats.moves_left = 0;
7988                 } else {
7989                     tempStats.line_is_book = 0;
7990                 }
7991
7992                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7993                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7994
7995                 SendProgramStatsToFrontend( cps, &tempStats );
7996
7997                 /*
7998                     [AS] Protect the thinkOutput buffer from overflow... this
7999                     is only useful if buf1 hasn't overflowed first!
8000                 */
8001                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8002                          plylev,
8003                          (gameMode == TwoMachinesPlay ?
8004                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8005                          ((double) curscore) / 100.0,
8006                          prefixHint ? lastHint : "",
8007                          prefixHint ? " " : "" );
8008
8009                 if( buf1[0] != NULLCHAR ) {
8010                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8011
8012                     if( strlen(buf1) > max_len ) {
8013                         if( appData.debugMode) {
8014                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8015                         }
8016                         buf1[max_len+1] = '\0';
8017                     }
8018
8019                     strcat( thinkOutput, buf1 );
8020                 }
8021
8022                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8023                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8024                     DisplayMove(currentMove - 1);
8025                 }
8026                 return;
8027
8028             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8029                 /* crafty (9.25+) says "(only move) <move>"
8030                  * if there is only 1 legal move
8031                  */
8032                 sscanf(p, "(only move) %s", buf1);
8033                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8034                 sprintf(programStats.movelist, "%s (only move)", buf1);
8035                 programStats.depth = 1;
8036                 programStats.nr_moves = 1;
8037                 programStats.moves_left = 1;
8038                 programStats.nodes = 1;
8039                 programStats.time = 1;
8040                 programStats.got_only_move = 1;
8041
8042                 /* Not really, but we also use this member to
8043                    mean "line isn't going to change" (Crafty
8044                    isn't searching, so stats won't change) */
8045                 programStats.line_is_book = 1;
8046
8047                 SendProgramStatsToFrontend( cps, &programStats );
8048
8049                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8050                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8051                     DisplayMove(currentMove - 1);
8052                 }
8053                 return;
8054             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8055                               &time, &nodes, &plylev, &mvleft,
8056                               &mvtot, mvname) >= 5) {
8057                 /* The stat01: line is from Crafty (9.29+) in response
8058                    to the "." command */
8059                 programStats.seen_stat = 1;
8060                 cps->maybeThinking = TRUE;
8061
8062                 if (programStats.got_only_move || !appData.periodicUpdates)
8063                   return;
8064
8065                 programStats.depth = plylev;
8066                 programStats.time = time;
8067                 programStats.nodes = nodes;
8068                 programStats.moves_left = mvleft;
8069                 programStats.nr_moves = mvtot;
8070                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8071                 programStats.ok_to_send = 1;
8072                 programStats.movelist[0] = '\0';
8073
8074                 SendProgramStatsToFrontend( cps, &programStats );
8075
8076                 return;
8077
8078             } else if (strncmp(message,"++",2) == 0) {
8079                 /* Crafty 9.29+ outputs this */
8080                 programStats.got_fail = 2;
8081                 return;
8082
8083             } else if (strncmp(message,"--",2) == 0) {
8084                 /* Crafty 9.29+ outputs this */
8085                 programStats.got_fail = 1;
8086                 return;
8087
8088             } else if (thinkOutput[0] != NULLCHAR &&
8089                        strncmp(message, "    ", 4) == 0) {
8090                 unsigned message_len;
8091
8092                 p = message;
8093                 while (*p && *p == ' ') p++;
8094
8095                 message_len = strlen( p );
8096
8097                 /* [AS] Avoid buffer overflow */
8098                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8099                     strcat(thinkOutput, " ");
8100                     strcat(thinkOutput, p);
8101                 }
8102
8103                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8104                     strcat(programStats.movelist, " ");
8105                     strcat(programStats.movelist, p);
8106                 }
8107
8108                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8109                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8110                     DisplayMove(currentMove - 1);
8111                 }
8112                 return;
8113             }
8114         }
8115         else {
8116             buf1[0] = NULLCHAR;
8117
8118             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8119                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8120             {
8121                 ChessProgramStats cpstats;
8122
8123                 if (plyext != ' ' && plyext != '\t') {
8124                     time *= 100;
8125                 }
8126
8127                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8128                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8129                     curscore = -curscore;
8130                 }
8131
8132                 cpstats.depth = plylev;
8133                 cpstats.nodes = nodes;
8134                 cpstats.time = time;
8135                 cpstats.score = curscore;
8136                 cpstats.got_only_move = 0;
8137                 cpstats.movelist[0] = '\0';
8138
8139                 if (buf1[0] != NULLCHAR) {
8140                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8141                 }
8142
8143                 cpstats.ok_to_send = 0;
8144                 cpstats.line_is_book = 0;
8145                 cpstats.nr_moves = 0;
8146                 cpstats.moves_left = 0;
8147
8148                 SendProgramStatsToFrontend( cps, &cpstats );
8149             }
8150         }
8151     }
8152 }
8153
8154
8155 /* Parse a game score from the character string "game", and
8156    record it as the history of the current game.  The game
8157    score is NOT assumed to start from the standard position.
8158    The display is not updated in any way.
8159    */
8160 void
8161 ParseGameHistory(game)
8162      char *game;
8163 {
8164     ChessMove moveType;
8165     int fromX, fromY, toX, toY, boardIndex;
8166     char promoChar;
8167     char *p, *q;
8168     char buf[MSG_SIZ];
8169
8170     if (appData.debugMode)
8171       fprintf(debugFP, "Parsing game history: %s\n", game);
8172
8173     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8174     gameInfo.site = StrSave(appData.icsHost);
8175     gameInfo.date = PGNDate();
8176     gameInfo.round = StrSave("-");
8177
8178     /* Parse out names of players */
8179     while (*game == ' ') game++;
8180     p = buf;
8181     while (*game != ' ') *p++ = *game++;
8182     *p = NULLCHAR;
8183     gameInfo.white = StrSave(buf);
8184     while (*game == ' ') game++;
8185     p = buf;
8186     while (*game != ' ' && *game != '\n') *p++ = *game++;
8187     *p = NULLCHAR;
8188     gameInfo.black = StrSave(buf);
8189
8190     /* Parse moves */
8191     boardIndex = blackPlaysFirst ? 1 : 0;
8192     yynewstr(game);
8193     for (;;) {
8194         yyboardindex = boardIndex;
8195         moveType = (ChessMove) yylex();
8196         switch (moveType) {
8197           case IllegalMove:             /* maybe suicide chess, etc. */
8198   if (appData.debugMode) {
8199     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8200     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8201     setbuf(debugFP, NULL);
8202   }
8203           case WhitePromotion:
8204           case BlackPromotion:
8205           case WhiteNonPromotion:
8206           case BlackNonPromotion:
8207           case NormalMove:
8208           case WhiteCapturesEnPassant:
8209           case BlackCapturesEnPassant:
8210           case WhiteKingSideCastle:
8211           case WhiteQueenSideCastle:
8212           case BlackKingSideCastle:
8213           case BlackQueenSideCastle:
8214           case WhiteKingSideCastleWild:
8215           case WhiteQueenSideCastleWild:
8216           case BlackKingSideCastleWild:
8217           case BlackQueenSideCastleWild:
8218           /* PUSH Fabien */
8219           case WhiteHSideCastleFR:
8220           case WhiteASideCastleFR:
8221           case BlackHSideCastleFR:
8222           case BlackASideCastleFR:
8223           /* POP Fabien */
8224             fromX = currentMoveString[0] - AAA;
8225             fromY = currentMoveString[1] - ONE;
8226             toX = currentMoveString[2] - AAA;
8227             toY = currentMoveString[3] - ONE;
8228             promoChar = currentMoveString[4];
8229             break;
8230           case WhiteDrop:
8231           case BlackDrop:
8232             fromX = moveType == WhiteDrop ?
8233               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8234             (int) CharToPiece(ToLower(currentMoveString[0]));
8235             fromY = DROP_RANK;
8236             toX = currentMoveString[2] - AAA;
8237             toY = currentMoveString[3] - ONE;
8238             promoChar = NULLCHAR;
8239             break;
8240           case AmbiguousMove:
8241             /* bug? */
8242             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8243   if (appData.debugMode) {
8244     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8245     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8246     setbuf(debugFP, NULL);
8247   }
8248             DisplayError(buf, 0);
8249             return;
8250           case ImpossibleMove:
8251             /* bug? */
8252             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8253   if (appData.debugMode) {
8254     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8255     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8256     setbuf(debugFP, NULL);
8257   }
8258             DisplayError(buf, 0);
8259             return;
8260           case EndOfFile:
8261             if (boardIndex < backwardMostMove) {
8262                 /* Oops, gap.  How did that happen? */
8263                 DisplayError(_("Gap in move list"), 0);
8264                 return;
8265             }
8266             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8267             if (boardIndex > forwardMostMove) {
8268                 forwardMostMove = boardIndex;
8269             }
8270             return;
8271           case ElapsedTime:
8272             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8273                 strcat(parseList[boardIndex-1], " ");
8274                 strcat(parseList[boardIndex-1], yy_text);
8275             }
8276             continue;
8277           case Comment:
8278           case PGNTag:
8279           case NAG:
8280           default:
8281             /* ignore */
8282             continue;
8283           case WhiteWins:
8284           case BlackWins:
8285           case GameIsDrawn:
8286           case GameUnfinished:
8287             if (gameMode == IcsExamining) {
8288                 if (boardIndex < backwardMostMove) {
8289                     /* Oops, gap.  How did that happen? */
8290                     return;
8291                 }
8292                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8293                 return;
8294             }
8295             gameInfo.result = moveType;
8296             p = strchr(yy_text, '{');
8297             if (p == NULL) p = strchr(yy_text, '(');
8298             if (p == NULL) {
8299                 p = yy_text;
8300                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8301             } else {
8302                 q = strchr(p, *p == '{' ? '}' : ')');
8303                 if (q != NULL) *q = NULLCHAR;
8304                 p++;
8305             }
8306             gameInfo.resultDetails = StrSave(p);
8307             continue;
8308         }
8309         if (boardIndex >= forwardMostMove &&
8310             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8311             backwardMostMove = blackPlaysFirst ? 1 : 0;
8312             return;
8313         }
8314         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8315                                  fromY, fromX, toY, toX, promoChar,
8316                                  parseList[boardIndex]);
8317         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8318         /* currentMoveString is set as a side-effect of yylex */
8319         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8320         strcat(moveList[boardIndex], "\n");
8321         boardIndex++;
8322         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8323         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8324           case MT_NONE:
8325           case MT_STALEMATE:
8326           default:
8327             break;
8328           case MT_CHECK:
8329             if(gameInfo.variant != VariantShogi)
8330                 strcat(parseList[boardIndex - 1], "+");
8331             break;
8332           case MT_CHECKMATE:
8333           case MT_STAINMATE:
8334             strcat(parseList[boardIndex - 1], "#");
8335             break;
8336         }
8337     }
8338 }
8339
8340
8341 /* Apply a move to the given board  */
8342 void
8343 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8344      int fromX, fromY, toX, toY;
8345      int promoChar;
8346      Board board;
8347 {
8348   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8349   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8350
8351     /* [HGM] compute & store e.p. status and castling rights for new position */
8352     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8353
8354       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8355       oldEP = (signed char)board[EP_STATUS];
8356       board[EP_STATUS] = EP_NONE;
8357
8358       if( board[toY][toX] != EmptySquare )
8359            board[EP_STATUS] = EP_CAPTURE;
8360
8361   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8362   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8363        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8364
8365   if (fromY == DROP_RANK) {
8366         /* must be first */
8367         piece = board[toY][toX] = (ChessSquare) fromX;
8368   } else {
8369       int i;
8370
8371       if( board[fromY][fromX] == WhitePawn ) {
8372            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8373                board[EP_STATUS] = EP_PAWN_MOVE;
8374            if( toY-fromY==2) {
8375                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8376                         gameInfo.variant != VariantBerolina || toX < fromX)
8377                       board[EP_STATUS] = toX | berolina;
8378                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8379                         gameInfo.variant != VariantBerolina || toX > fromX)
8380                       board[EP_STATUS] = toX;
8381            }
8382       } else
8383       if( board[fromY][fromX] == BlackPawn ) {
8384            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8385                board[EP_STATUS] = EP_PAWN_MOVE;
8386            if( toY-fromY== -2) {
8387                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8388                         gameInfo.variant != VariantBerolina || toX < fromX)
8389                       board[EP_STATUS] = toX | berolina;
8390                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8391                         gameInfo.variant != VariantBerolina || toX > fromX)
8392                       board[EP_STATUS] = toX;
8393            }
8394        }
8395
8396        for(i=0; i<nrCastlingRights; i++) {
8397            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8398               board[CASTLING][i] == toX   && castlingRank[i] == toY
8399              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8400        }
8401
8402      if (fromX == toX && fromY == toY) return;
8403
8404      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8405      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8406      if(gameInfo.variant == VariantKnightmate)
8407          king += (int) WhiteUnicorn - (int) WhiteKing;
8408
8409     /* Code added by Tord: */
8410     /* FRC castling assumed when king captures friendly rook. */
8411     if (board[fromY][fromX] == WhiteKing &&
8412              board[toY][toX] == WhiteRook) {
8413       board[fromY][fromX] = EmptySquare;
8414       board[toY][toX] = EmptySquare;
8415       if(toX > fromX) {
8416         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8417       } else {
8418         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8419       }
8420     } else if (board[fromY][fromX] == BlackKing &&
8421                board[toY][toX] == BlackRook) {
8422       board[fromY][fromX] = EmptySquare;
8423       board[toY][toX] = EmptySquare;
8424       if(toX > fromX) {
8425         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8426       } else {
8427         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8428       }
8429     /* End of code added by Tord */
8430
8431     } else if (board[fromY][fromX] == king
8432         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8433         && toY == fromY && toX > fromX+1) {
8434         board[fromY][fromX] = EmptySquare;
8435         board[toY][toX] = king;
8436         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8437         board[fromY][BOARD_RGHT-1] = EmptySquare;
8438     } else if (board[fromY][fromX] == king
8439         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8440                && toY == fromY && toX < fromX-1) {
8441         board[fromY][fromX] = EmptySquare;
8442         board[toY][toX] = king;
8443         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8444         board[fromY][BOARD_LEFT] = EmptySquare;
8445     } else if (board[fromY][fromX] == WhitePawn
8446                && toY >= BOARD_HEIGHT-promoRank
8447                && gameInfo.variant != VariantXiangqi
8448                ) {
8449         /* white pawn promotion */
8450         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8451         if (board[toY][toX] == EmptySquare) {
8452             board[toY][toX] = WhiteQueen;
8453         }
8454         if(gameInfo.variant==VariantBughouse ||
8455            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8456             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8457         board[fromY][fromX] = EmptySquare;
8458     } else if ((fromY == BOARD_HEIGHT-4)
8459                && (toX != fromX)
8460                && gameInfo.variant != VariantXiangqi
8461                && gameInfo.variant != VariantBerolina
8462                && (board[fromY][fromX] == WhitePawn)
8463                && (board[toY][toX] == EmptySquare)) {
8464         board[fromY][fromX] = EmptySquare;
8465         board[toY][toX] = WhitePawn;
8466         captured = board[toY - 1][toX];
8467         board[toY - 1][toX] = EmptySquare;
8468     } else if ((fromY == BOARD_HEIGHT-4)
8469                && (toX == fromX)
8470                && gameInfo.variant == VariantBerolina
8471                && (board[fromY][fromX] == WhitePawn)
8472                && (board[toY][toX] == EmptySquare)) {
8473         board[fromY][fromX] = EmptySquare;
8474         board[toY][toX] = WhitePawn;
8475         if(oldEP & EP_BEROLIN_A) {
8476                 captured = board[fromY][fromX-1];
8477                 board[fromY][fromX-1] = EmptySquare;
8478         }else{  captured = board[fromY][fromX+1];
8479                 board[fromY][fromX+1] = EmptySquare;
8480         }
8481     } else if (board[fromY][fromX] == king
8482         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8483                && toY == fromY && toX > fromX+1) {
8484         board[fromY][fromX] = EmptySquare;
8485         board[toY][toX] = king;
8486         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8487         board[fromY][BOARD_RGHT-1] = EmptySquare;
8488     } else if (board[fromY][fromX] == king
8489         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8490                && toY == fromY && toX < fromX-1) {
8491         board[fromY][fromX] = EmptySquare;
8492         board[toY][toX] = king;
8493         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8494         board[fromY][BOARD_LEFT] = EmptySquare;
8495     } else if (fromY == 7 && fromX == 3
8496                && board[fromY][fromX] == BlackKing
8497                && toY == 7 && toX == 5) {
8498         board[fromY][fromX] = EmptySquare;
8499         board[toY][toX] = BlackKing;
8500         board[fromY][7] = EmptySquare;
8501         board[toY][4] = BlackRook;
8502     } else if (fromY == 7 && fromX == 3
8503                && board[fromY][fromX] == BlackKing
8504                && toY == 7 && toX == 1) {
8505         board[fromY][fromX] = EmptySquare;
8506         board[toY][toX] = BlackKing;
8507         board[fromY][0] = EmptySquare;
8508         board[toY][2] = BlackRook;
8509     } else if (board[fromY][fromX] == BlackPawn
8510                && toY < promoRank
8511                && gameInfo.variant != VariantXiangqi
8512                ) {
8513         /* black pawn promotion */
8514         board[toY][toX] = CharToPiece(ToLower(promoChar));
8515         if (board[toY][toX] == EmptySquare) {
8516             board[toY][toX] = BlackQueen;
8517         }
8518         if(gameInfo.variant==VariantBughouse ||
8519            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8520             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8521         board[fromY][fromX] = EmptySquare;
8522     } else if ((fromY == 3)
8523                && (toX != fromX)
8524                && gameInfo.variant != VariantXiangqi
8525                && gameInfo.variant != VariantBerolina
8526                && (board[fromY][fromX] == BlackPawn)
8527                && (board[toY][toX] == EmptySquare)) {
8528         board[fromY][fromX] = EmptySquare;
8529         board[toY][toX] = BlackPawn;
8530         captured = board[toY + 1][toX];
8531         board[toY + 1][toX] = EmptySquare;
8532     } else if ((fromY == 3)
8533                && (toX == fromX)
8534                && gameInfo.variant == VariantBerolina
8535                && (board[fromY][fromX] == BlackPawn)
8536                && (board[toY][toX] == EmptySquare)) {
8537         board[fromY][fromX] = EmptySquare;
8538         board[toY][toX] = BlackPawn;
8539         if(oldEP & EP_BEROLIN_A) {
8540                 captured = board[fromY][fromX-1];
8541                 board[fromY][fromX-1] = EmptySquare;
8542         }else{  captured = board[fromY][fromX+1];
8543                 board[fromY][fromX+1] = EmptySquare;
8544         }
8545     } else {
8546         board[toY][toX] = board[fromY][fromX];
8547         board[fromY][fromX] = EmptySquare;
8548     }
8549   }
8550
8551     if (gameInfo.holdingsWidth != 0) {
8552
8553       /* !!A lot more code needs to be written to support holdings  */
8554       /* [HGM] OK, so I have written it. Holdings are stored in the */
8555       /* penultimate board files, so they are automaticlly stored   */
8556       /* in the game history.                                       */
8557       if (fromY == DROP_RANK) {
8558         /* Delete from holdings, by decreasing count */
8559         /* and erasing image if necessary            */
8560         p = (int) fromX;
8561         if(p < (int) BlackPawn) { /* white drop */
8562              p -= (int)WhitePawn;
8563                  p = PieceToNumber((ChessSquare)p);
8564              if(p >= gameInfo.holdingsSize) p = 0;
8565              if(--board[p][BOARD_WIDTH-2] <= 0)
8566                   board[p][BOARD_WIDTH-1] = EmptySquare;
8567              if((int)board[p][BOARD_WIDTH-2] < 0)
8568                         board[p][BOARD_WIDTH-2] = 0;
8569         } else {                  /* black drop */
8570              p -= (int)BlackPawn;
8571                  p = PieceToNumber((ChessSquare)p);
8572              if(p >= gameInfo.holdingsSize) p = 0;
8573              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8574                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8575              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8576                         board[BOARD_HEIGHT-1-p][1] = 0;
8577         }
8578       }
8579       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8580           && gameInfo.variant != VariantBughouse        ) {
8581         /* [HGM] holdings: Add to holdings, if holdings exist */
8582         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8583                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8584                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8585         }
8586         p = (int) captured;
8587         if (p >= (int) BlackPawn) {
8588           p -= (int)BlackPawn;
8589           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8590                   /* in Shogi restore piece to its original  first */
8591                   captured = (ChessSquare) (DEMOTED captured);
8592                   p = DEMOTED p;
8593           }
8594           p = PieceToNumber((ChessSquare)p);
8595           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8596           board[p][BOARD_WIDTH-2]++;
8597           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8598         } else {
8599           p -= (int)WhitePawn;
8600           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8601                   captured = (ChessSquare) (DEMOTED captured);
8602                   p = DEMOTED p;
8603           }
8604           p = PieceToNumber((ChessSquare)p);
8605           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8606           board[BOARD_HEIGHT-1-p][1]++;
8607           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8608         }
8609       }
8610     } else if (gameInfo.variant == VariantAtomic) {
8611       if (captured != EmptySquare) {
8612         int y, x;
8613         for (y = toY-1; y <= toY+1; y++) {
8614           for (x = toX-1; x <= toX+1; x++) {
8615             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8616                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8617               board[y][x] = EmptySquare;
8618             }
8619           }
8620         }
8621         board[toY][toX] = EmptySquare;
8622       }
8623     }
8624     if(promoChar == '+') {
8625         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8626         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8627     } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
8628         board[toY][toX] = CharToPiece(promoChar);
8629     }
8630     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8631                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8632         // [HGM] superchess: take promotion piece out of holdings
8633         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8634         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8635             if(!--board[k][BOARD_WIDTH-2])
8636                 board[k][BOARD_WIDTH-1] = EmptySquare;
8637         } else {
8638             if(!--board[BOARD_HEIGHT-1-k][1])
8639                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8640         }
8641     }
8642
8643 }
8644
8645 /* Updates forwardMostMove */
8646 void
8647 MakeMove(fromX, fromY, toX, toY, promoChar)
8648      int fromX, fromY, toX, toY;
8649      int promoChar;
8650 {
8651 //    forwardMostMove++; // [HGM] bare: moved downstream
8652
8653     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8654         int timeLeft; static int lastLoadFlag=0; int king, piece;
8655         piece = boards[forwardMostMove][fromY][fromX];
8656         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8657         if(gameInfo.variant == VariantKnightmate)
8658             king += (int) WhiteUnicorn - (int) WhiteKing;
8659         if(forwardMostMove == 0) {
8660             if(blackPlaysFirst)
8661                 fprintf(serverMoves, "%s;", second.tidy);
8662             fprintf(serverMoves, "%s;", first.tidy);
8663             if(!blackPlaysFirst)
8664                 fprintf(serverMoves, "%s;", second.tidy);
8665         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8666         lastLoadFlag = loadFlag;
8667         // print base move
8668         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8669         // print castling suffix
8670         if( toY == fromY && piece == king ) {
8671             if(toX-fromX > 1)
8672                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8673             if(fromX-toX >1)
8674                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8675         }
8676         // e.p. suffix
8677         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8678              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8679              boards[forwardMostMove][toY][toX] == EmptySquare
8680              && fromX != toX && fromY != toY)
8681                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8682         // promotion suffix
8683         if(promoChar != NULLCHAR)
8684                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8685         if(!loadFlag) {
8686             fprintf(serverMoves, "/%d/%d",
8687                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8688             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8689             else                      timeLeft = blackTimeRemaining/1000;
8690             fprintf(serverMoves, "/%d", timeLeft);
8691         }
8692         fflush(serverMoves);
8693     }
8694
8695     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8696       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8697                         0, 1);
8698       return;
8699     }
8700     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8701     if (commentList[forwardMostMove+1] != NULL) {
8702         free(commentList[forwardMostMove+1]);
8703         commentList[forwardMostMove+1] = NULL;
8704     }
8705     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8706     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8707     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8708     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8709     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8710     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8711     gameInfo.result = GameUnfinished;
8712     if (gameInfo.resultDetails != NULL) {
8713         free(gameInfo.resultDetails);
8714         gameInfo.resultDetails = NULL;
8715     }
8716     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8717                               moveList[forwardMostMove - 1]);
8718     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8719                              PosFlags(forwardMostMove - 1),
8720                              fromY, fromX, toY, toX, promoChar,
8721                              parseList[forwardMostMove - 1]);
8722     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8723       case MT_NONE:
8724       case MT_STALEMATE:
8725       default:
8726         break;
8727       case MT_CHECK:
8728         if(gameInfo.variant != VariantShogi)
8729             strcat(parseList[forwardMostMove - 1], "+");
8730         break;
8731       case MT_CHECKMATE:
8732       case MT_STAINMATE:
8733         strcat(parseList[forwardMostMove - 1], "#");
8734         break;
8735     }
8736     if (appData.debugMode) {
8737         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8738     }
8739
8740 }
8741
8742 /* Updates currentMove if not pausing */
8743 void
8744 ShowMove(fromX, fromY, toX, toY)
8745 {
8746     int instant = (gameMode == PlayFromGameFile) ?
8747         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8748     if(appData.noGUI) return;
8749     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8750         if (!instant) {
8751             if (forwardMostMove == currentMove + 1) {
8752                 AnimateMove(boards[forwardMostMove - 1],
8753                             fromX, fromY, toX, toY);
8754             }
8755             if (appData.highlightLastMove) {
8756                 SetHighlights(fromX, fromY, toX, toY);
8757             }
8758         }
8759         currentMove = forwardMostMove;
8760     }
8761
8762     if (instant) return;
8763
8764     DisplayMove(currentMove - 1);
8765     DrawPosition(FALSE, boards[currentMove]);
8766     DisplayBothClocks();
8767     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8768 }
8769
8770 void SendEgtPath(ChessProgramState *cps)
8771 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8772         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8773
8774         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8775
8776         while(*p) {
8777             char c, *q = name+1, *r, *s;
8778
8779             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8780             while(*p && *p != ',') *q++ = *p++;
8781             *q++ = ':'; *q = 0;
8782             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8783                 strcmp(name, ",nalimov:") == 0 ) {
8784                 // take nalimov path from the menu-changeable option first, if it is defined
8785               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8786                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8787             } else
8788             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8789                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8790                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8791                 s = r = StrStr(s, ":") + 1; // beginning of path info
8792                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8793                 c = *r; *r = 0;             // temporarily null-terminate path info
8794                     *--q = 0;               // strip of trailig ':' from name
8795                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8796                 *r = c;
8797                 SendToProgram(buf,cps);     // send egtbpath command for this format
8798             }
8799             if(*p == ',') p++; // read away comma to position for next format name
8800         }
8801 }
8802
8803 void
8804 InitChessProgram(cps, setup)
8805      ChessProgramState *cps;
8806      int setup; /* [HGM] needed to setup FRC opening position */
8807 {
8808     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8809     if (appData.noChessProgram) return;
8810     hintRequested = FALSE;
8811     bookRequested = FALSE;
8812
8813     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8814     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8815     if(cps->memSize) { /* [HGM] memory */
8816       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8817         SendToProgram(buf, cps);
8818     }
8819     SendEgtPath(cps); /* [HGM] EGT */
8820     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8821       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8822         SendToProgram(buf, cps);
8823     }
8824
8825     SendToProgram(cps->initString, cps);
8826     if (gameInfo.variant != VariantNormal &&
8827         gameInfo.variant != VariantLoadable
8828         /* [HGM] also send variant if board size non-standard */
8829         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8830                                             ) {
8831       char *v = VariantName(gameInfo.variant);
8832       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8833         /* [HGM] in protocol 1 we have to assume all variants valid */
8834         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8835         DisplayFatalError(buf, 0, 1);
8836         return;
8837       }
8838
8839       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8840       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8841       if( gameInfo.variant == VariantXiangqi )
8842            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8843       if( gameInfo.variant == VariantShogi )
8844            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8845       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8846            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8847       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8848                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8849            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8850       if( gameInfo.variant == VariantCourier )
8851            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8852       if( gameInfo.variant == VariantSuper )
8853            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8854       if( gameInfo.variant == VariantGreat )
8855            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8856
8857       if(overruled) {
8858         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8859                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8860            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8861            if(StrStr(cps->variants, b) == NULL) {
8862                // specific sized variant not known, check if general sizing allowed
8863                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8864                    if(StrStr(cps->variants, "boardsize") == NULL) {
8865                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8866                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8867                        DisplayFatalError(buf, 0, 1);
8868                        return;
8869                    }
8870                    /* [HGM] here we really should compare with the maximum supported board size */
8871                }
8872            }
8873       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8874       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8875       SendToProgram(buf, cps);
8876     }
8877     currentlyInitializedVariant = gameInfo.variant;
8878
8879     /* [HGM] send opening position in FRC to first engine */
8880     if(setup) {
8881           SendToProgram("force\n", cps);
8882           SendBoard(cps, 0);
8883           /* engine is now in force mode! Set flag to wake it up after first move. */
8884           setboardSpoiledMachineBlack = 1;
8885     }
8886
8887     if (cps->sendICS) {
8888       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8889       SendToProgram(buf, cps);
8890     }
8891     cps->maybeThinking = FALSE;
8892     cps->offeredDraw = 0;
8893     if (!appData.icsActive) {
8894         SendTimeControl(cps, movesPerSession, timeControl,
8895                         timeIncrement, appData.searchDepth,
8896                         searchTime);
8897     }
8898     if (appData.showThinking
8899         // [HGM] thinking: four options require thinking output to be sent
8900         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8901                                 ) {
8902         SendToProgram("post\n", cps);
8903     }
8904     SendToProgram("hard\n", cps);
8905     if (!appData.ponderNextMove) {
8906         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8907            it without being sure what state we are in first.  "hard"
8908            is not a toggle, so that one is OK.
8909          */
8910         SendToProgram("easy\n", cps);
8911     }
8912     if (cps->usePing) {
8913       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8914       SendToProgram(buf, cps);
8915     }
8916     cps->initDone = TRUE;
8917 }
8918
8919
8920 void
8921 StartChessProgram(cps)
8922      ChessProgramState *cps;
8923 {
8924     char buf[MSG_SIZ];
8925     int err;
8926
8927     if (appData.noChessProgram) return;
8928     cps->initDone = FALSE;
8929
8930     if (strcmp(cps->host, "localhost") == 0) {
8931         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8932     } else if (*appData.remoteShell == NULLCHAR) {
8933         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8934     } else {
8935         if (*appData.remoteUser == NULLCHAR) {
8936           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8937                     cps->program);
8938         } else {
8939           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8940                     cps->host, appData.remoteUser, cps->program);
8941         }
8942         err = StartChildProcess(buf, "", &cps->pr);
8943     }
8944
8945     if (err != 0) {
8946       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8947         DisplayFatalError(buf, err, 1);
8948         cps->pr = NoProc;
8949         cps->isr = NULL;
8950         return;
8951     }
8952
8953     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8954     if (cps->protocolVersion > 1) {
8955       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8956       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8957       cps->comboCnt = 0;  //                and values of combo boxes
8958       SendToProgram(buf, cps);
8959     } else {
8960       SendToProgram("xboard\n", cps);
8961     }
8962 }
8963
8964
8965 void
8966 TwoMachinesEventIfReady P((void))
8967 {
8968   if (first.lastPing != first.lastPong) {
8969     DisplayMessage("", _("Waiting for first chess program"));
8970     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8971     return;
8972   }
8973   if (second.lastPing != second.lastPong) {
8974     DisplayMessage("", _("Waiting for second chess program"));
8975     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8976     return;
8977   }
8978   ThawUI();
8979   TwoMachinesEvent();
8980 }
8981
8982 void
8983 NextMatchGame P((void))
8984 {
8985     int index; /* [HGM] autoinc: step load index during match */
8986     Reset(FALSE, TRUE);
8987     if (*appData.loadGameFile != NULLCHAR) {
8988         index = appData.loadGameIndex;
8989         if(index < 0) { // [HGM] autoinc
8990             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8991             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8992         }
8993         LoadGameFromFile(appData.loadGameFile,
8994                          index,
8995                          appData.loadGameFile, FALSE);
8996     } else if (*appData.loadPositionFile != NULLCHAR) {
8997         index = appData.loadPositionIndex;
8998         if(index < 0) { // [HGM] autoinc
8999             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
9000             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
9001         }
9002         LoadPositionFromFile(appData.loadPositionFile,
9003                              index,
9004                              appData.loadPositionFile);
9005     }
9006     TwoMachinesEventIfReady();
9007 }
9008
9009 void UserAdjudicationEvent( int result )
9010 {
9011     ChessMove gameResult = GameIsDrawn;
9012
9013     if( result > 0 ) {
9014         gameResult = WhiteWins;
9015     }
9016     else if( result < 0 ) {
9017         gameResult = BlackWins;
9018     }
9019
9020     if( gameMode == TwoMachinesPlay ) {
9021         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9022     }
9023 }
9024
9025
9026 // [HGM] save: calculate checksum of game to make games easily identifiable
9027 int StringCheckSum(char *s)
9028 {
9029         int i = 0;
9030         if(s==NULL) return 0;
9031         while(*s) i = i*259 + *s++;
9032         return i;
9033 }
9034
9035 int GameCheckSum()
9036 {
9037         int i, sum=0;
9038         for(i=backwardMostMove; i<forwardMostMove; i++) {
9039                 sum += pvInfoList[i].depth;
9040                 sum += StringCheckSum(parseList[i]);
9041                 sum += StringCheckSum(commentList[i]);
9042                 sum *= 261;
9043         }
9044         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9045         return sum + StringCheckSum(commentList[i]);
9046 } // end of save patch
9047
9048 void
9049 GameEnds(result, resultDetails, whosays)
9050      ChessMove result;
9051      char *resultDetails;
9052      int whosays;
9053 {
9054     GameMode nextGameMode;
9055     int isIcsGame;
9056     char buf[MSG_SIZ], popupRequested = 0;
9057
9058     if(endingGame) return; /* [HGM] crash: forbid recursion */
9059     endingGame = 1;
9060     if(twoBoards) { // [HGM] dual: switch back to one board
9061         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9062         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9063     }
9064     if (appData.debugMode) {
9065       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9066               result, resultDetails ? resultDetails : "(null)", whosays);
9067     }
9068
9069     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9070
9071     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9072         /* If we are playing on ICS, the server decides when the
9073            game is over, but the engine can offer to draw, claim
9074            a draw, or resign.
9075          */
9076 #if ZIPPY
9077         if (appData.zippyPlay && first.initDone) {
9078             if (result == GameIsDrawn) {
9079                 /* In case draw still needs to be claimed */
9080                 SendToICS(ics_prefix);
9081                 SendToICS("draw\n");
9082             } else if (StrCaseStr(resultDetails, "resign")) {
9083                 SendToICS(ics_prefix);
9084                 SendToICS("resign\n");
9085             }
9086         }
9087 #endif
9088         endingGame = 0; /* [HGM] crash */
9089         return;
9090     }
9091
9092     /* If we're loading the game from a file, stop */
9093     if (whosays == GE_FILE) {
9094       (void) StopLoadGameTimer();
9095       gameFileFP = NULL;
9096     }
9097
9098     /* Cancel draw offers */
9099     first.offeredDraw = second.offeredDraw = 0;
9100
9101     /* If this is an ICS game, only ICS can really say it's done;
9102        if not, anyone can. */
9103     isIcsGame = (gameMode == IcsPlayingWhite ||
9104                  gameMode == IcsPlayingBlack ||
9105                  gameMode == IcsObserving    ||
9106                  gameMode == IcsExamining);
9107
9108     if (!isIcsGame || whosays == GE_ICS) {
9109         /* OK -- not an ICS game, or ICS said it was done */
9110         StopClocks();
9111         if (!isIcsGame && !appData.noChessProgram)
9112           SetUserThinkingEnables();
9113
9114         /* [HGM] if a machine claims the game end we verify this claim */
9115         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9116             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9117                 char claimer;
9118                 ChessMove trueResult = (ChessMove) -1;
9119
9120                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9121                                             first.twoMachinesColor[0] :
9122                                             second.twoMachinesColor[0] ;
9123
9124                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9125                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9126                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9127                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9128                 } else
9129                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9130                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9131                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9132                 } else
9133                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9134                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9135                 }
9136
9137                 // now verify win claims, but not in drop games, as we don't understand those yet
9138                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9139                                                  || gameInfo.variant == VariantGreat) &&
9140                     (result == WhiteWins && claimer == 'w' ||
9141                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9142                       if (appData.debugMode) {
9143                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9144                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9145                       }
9146                       if(result != trueResult) {
9147                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9148                               result = claimer == 'w' ? BlackWins : WhiteWins;
9149                               resultDetails = buf;
9150                       }
9151                 } else
9152                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9153                     && (forwardMostMove <= backwardMostMove ||
9154                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9155                         (claimer=='b')==(forwardMostMove&1))
9156                                                                                   ) {
9157                       /* [HGM] verify: draws that were not flagged are false claims */
9158                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9159                       result = claimer == 'w' ? BlackWins : WhiteWins;
9160                       resultDetails = buf;
9161                 }
9162                 /* (Claiming a loss is accepted no questions asked!) */
9163             }
9164             /* [HGM] bare: don't allow bare King to win */
9165             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9166                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9167                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9168                && result != GameIsDrawn)
9169             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9170                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9171                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9172                         if(p >= 0 && p <= (int)WhiteKing) k++;
9173                 }
9174                 if (appData.debugMode) {
9175                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9176                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9177                 }
9178                 if(k <= 1) {
9179                         result = GameIsDrawn;
9180                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9181                         resultDetails = buf;
9182                 }
9183             }
9184         }
9185
9186
9187         if(serverMoves != NULL && !loadFlag) { char c = '=';
9188             if(result==WhiteWins) c = '+';
9189             if(result==BlackWins) c = '-';
9190             if(resultDetails != NULL)
9191                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9192         }
9193         if (resultDetails != NULL) {
9194             gameInfo.result = result;
9195             gameInfo.resultDetails = StrSave(resultDetails);
9196
9197             /* display last move only if game was not loaded from file */
9198             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9199                 DisplayMove(currentMove - 1);
9200
9201             if (forwardMostMove != 0) {
9202                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9203                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9204                                                                 ) {
9205                     if (*appData.saveGameFile != NULLCHAR) {
9206                         SaveGameToFile(appData.saveGameFile, TRUE);
9207                     } else if (appData.autoSaveGames) {
9208                         AutoSaveGame();
9209                     }
9210                     if (*appData.savePositionFile != NULLCHAR) {
9211                         SavePositionToFile(appData.savePositionFile);
9212                     }
9213                 }
9214             }
9215
9216             /* Tell program how game ended in case it is learning */
9217             /* [HGM] Moved this to after saving the PGN, just in case */
9218             /* engine died and we got here through time loss. In that */
9219             /* case we will get a fatal error writing the pipe, which */
9220             /* would otherwise lose us the PGN.                       */
9221             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9222             /* output during GameEnds should never be fatal anymore   */
9223             if (gameMode == MachinePlaysWhite ||
9224                 gameMode == MachinePlaysBlack ||
9225                 gameMode == TwoMachinesPlay ||
9226                 gameMode == IcsPlayingWhite ||
9227                 gameMode == IcsPlayingBlack ||
9228                 gameMode == BeginningOfGame) {
9229                 char buf[MSG_SIZ];
9230                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9231                         resultDetails);
9232                 if (first.pr != NoProc) {
9233                     SendToProgram(buf, &first);
9234                 }
9235                 if (second.pr != NoProc &&
9236                     gameMode == TwoMachinesPlay) {
9237                     SendToProgram(buf, &second);
9238                 }
9239             }
9240         }
9241
9242         if (appData.icsActive) {
9243             if (appData.quietPlay &&
9244                 (gameMode == IcsPlayingWhite ||
9245                  gameMode == IcsPlayingBlack)) {
9246                 SendToICS(ics_prefix);
9247                 SendToICS("set shout 1\n");
9248             }
9249             nextGameMode = IcsIdle;
9250             ics_user_moved = FALSE;
9251             /* clean up premove.  It's ugly when the game has ended and the
9252              * premove highlights are still on the board.
9253              */
9254             if (gotPremove) {
9255               gotPremove = FALSE;
9256               ClearPremoveHighlights();
9257               DrawPosition(FALSE, boards[currentMove]);
9258             }
9259             if (whosays == GE_ICS) {
9260                 switch (result) {
9261                 case WhiteWins:
9262                     if (gameMode == IcsPlayingWhite)
9263                         PlayIcsWinSound();
9264                     else if(gameMode == IcsPlayingBlack)
9265                         PlayIcsLossSound();
9266                     break;
9267                 case BlackWins:
9268                     if (gameMode == IcsPlayingBlack)
9269                         PlayIcsWinSound();
9270                     else if(gameMode == IcsPlayingWhite)
9271                         PlayIcsLossSound();
9272                     break;
9273                 case GameIsDrawn:
9274                     PlayIcsDrawSound();
9275                     break;
9276                 default:
9277                     PlayIcsUnfinishedSound();
9278                 }
9279             }
9280         } else if (gameMode == EditGame ||
9281                    gameMode == PlayFromGameFile ||
9282                    gameMode == AnalyzeMode ||
9283                    gameMode == AnalyzeFile) {
9284             nextGameMode = gameMode;
9285         } else {
9286             nextGameMode = EndOfGame;
9287         }
9288         pausing = FALSE;
9289         ModeHighlight();
9290     } else {
9291         nextGameMode = gameMode;
9292     }
9293
9294     if (appData.noChessProgram) {
9295         gameMode = nextGameMode;
9296         ModeHighlight();
9297         endingGame = 0; /* [HGM] crash */
9298         return;
9299     }
9300
9301     if (first.reuse) {
9302         /* Put first chess program into idle state */
9303         if (first.pr != NoProc &&
9304             (gameMode == MachinePlaysWhite ||
9305              gameMode == MachinePlaysBlack ||
9306              gameMode == TwoMachinesPlay ||
9307              gameMode == IcsPlayingWhite ||
9308              gameMode == IcsPlayingBlack ||
9309              gameMode == BeginningOfGame)) {
9310             SendToProgram("force\n", &first);
9311             if (first.usePing) {
9312               char buf[MSG_SIZ];
9313               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9314               SendToProgram(buf, &first);
9315             }
9316         }
9317     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9318         /* Kill off first chess program */
9319         if (first.isr != NULL)
9320           RemoveInputSource(first.isr);
9321         first.isr = NULL;
9322
9323         if (first.pr != NoProc) {
9324             ExitAnalyzeMode();
9325             DoSleep( appData.delayBeforeQuit );
9326             SendToProgram("quit\n", &first);
9327             DoSleep( appData.delayAfterQuit );
9328             DestroyChildProcess(first.pr, first.useSigterm);
9329         }
9330         first.pr = NoProc;
9331     }
9332     if (second.reuse) {
9333         /* Put second chess program into idle state */
9334         if (second.pr != NoProc &&
9335             gameMode == TwoMachinesPlay) {
9336             SendToProgram("force\n", &second);
9337             if (second.usePing) {
9338               char buf[MSG_SIZ];
9339               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9340               SendToProgram(buf, &second);
9341             }
9342         }
9343     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9344         /* Kill off second chess program */
9345         if (second.isr != NULL)
9346           RemoveInputSource(second.isr);
9347         second.isr = NULL;
9348
9349         if (second.pr != NoProc) {
9350             DoSleep( appData.delayBeforeQuit );
9351             SendToProgram("quit\n", &second);
9352             DoSleep( appData.delayAfterQuit );
9353             DestroyChildProcess(second.pr, second.useSigterm);
9354         }
9355         second.pr = NoProc;
9356     }
9357
9358     if (matchMode && gameMode == TwoMachinesPlay) {
9359         switch (result) {
9360         case WhiteWins:
9361           if (first.twoMachinesColor[0] == 'w') {
9362             first.matchWins++;
9363           } else {
9364             second.matchWins++;
9365           }
9366           break;
9367         case BlackWins:
9368           if (first.twoMachinesColor[0] == 'b') {
9369             first.matchWins++;
9370           } else {
9371             second.matchWins++;
9372           }
9373           break;
9374         default:
9375           break;
9376         }
9377         if (matchGame < appData.matchGames) {
9378             char *tmp;
9379             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9380                 tmp = first.twoMachinesColor;
9381                 first.twoMachinesColor = second.twoMachinesColor;
9382                 second.twoMachinesColor = tmp;
9383             }
9384             gameMode = nextGameMode;
9385             matchGame++;
9386             if(appData.matchPause>10000 || appData.matchPause<10)
9387                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9388             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9389             endingGame = 0; /* [HGM] crash */
9390             return;
9391         } else {
9392             gameMode = nextGameMode;
9393             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9394                      first.tidy, second.tidy,
9395                      first.matchWins, second.matchWins,
9396                      appData.matchGames - (first.matchWins + second.matchWins));
9397             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9398         }
9399     }
9400     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9401         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9402       ExitAnalyzeMode();
9403     gameMode = nextGameMode;
9404     ModeHighlight();
9405     endingGame = 0;  /* [HGM] crash */
9406     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9407       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9408         matchMode = FALSE; appData.matchGames = matchGame = 0;
9409         DisplayNote(buf);
9410       }
9411     }
9412 }
9413
9414 /* Assumes program was just initialized (initString sent).
9415    Leaves program in force mode. */
9416 void
9417 FeedMovesToProgram(cps, upto)
9418      ChessProgramState *cps;
9419      int upto;
9420 {
9421     int i;
9422
9423     if (appData.debugMode)
9424       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9425               startedFromSetupPosition ? "position and " : "",
9426               backwardMostMove, upto, cps->which);
9427     if(currentlyInitializedVariant != gameInfo.variant) {
9428       char buf[MSG_SIZ];
9429         // [HGM] variantswitch: make engine aware of new variant
9430         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9431                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9432         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9433         SendToProgram(buf, cps);
9434         currentlyInitializedVariant = gameInfo.variant;
9435     }
9436     SendToProgram("force\n", cps);
9437     if (startedFromSetupPosition) {
9438         SendBoard(cps, backwardMostMove);
9439     if (appData.debugMode) {
9440         fprintf(debugFP, "feedMoves\n");
9441     }
9442     }
9443     for (i = backwardMostMove; i < upto; i++) {
9444         SendMoveToProgram(i, cps);
9445     }
9446 }
9447
9448
9449 void
9450 ResurrectChessProgram()
9451 {
9452      /* The chess program may have exited.
9453         If so, restart it and feed it all the moves made so far. */
9454
9455     if (appData.noChessProgram || first.pr != NoProc) return;
9456
9457     StartChessProgram(&first);
9458     InitChessProgram(&first, FALSE);
9459     FeedMovesToProgram(&first, currentMove);
9460
9461     if (!first.sendTime) {
9462         /* can't tell gnuchess what its clock should read,
9463            so we bow to its notion. */
9464         ResetClocks();
9465         timeRemaining[0][currentMove] = whiteTimeRemaining;
9466         timeRemaining[1][currentMove] = blackTimeRemaining;
9467     }
9468
9469     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9470                 appData.icsEngineAnalyze) && first.analysisSupport) {
9471       SendToProgram("analyze\n", &first);
9472       first.analyzing = TRUE;
9473     }
9474 }
9475
9476 /*
9477  * Button procedures
9478  */
9479 void
9480 Reset(redraw, init)
9481      int redraw, init;
9482 {
9483     int i;
9484
9485     if (appData.debugMode) {
9486         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9487                 redraw, init, gameMode);
9488     }
9489     CleanupTail(); // [HGM] vari: delete any stored variations
9490     pausing = pauseExamInvalid = FALSE;
9491     startedFromSetupPosition = blackPlaysFirst = FALSE;
9492     firstMove = TRUE;
9493     whiteFlag = blackFlag = FALSE;
9494     userOfferedDraw = FALSE;
9495     hintRequested = bookRequested = FALSE;
9496     first.maybeThinking = FALSE;
9497     second.maybeThinking = FALSE;
9498     first.bookSuspend = FALSE; // [HGM] book
9499     second.bookSuspend = FALSE;
9500     thinkOutput[0] = NULLCHAR;
9501     lastHint[0] = NULLCHAR;
9502     ClearGameInfo(&gameInfo);
9503     gameInfo.variant = StringToVariant(appData.variant);
9504     ics_user_moved = ics_clock_paused = FALSE;
9505     ics_getting_history = H_FALSE;
9506     ics_gamenum = -1;
9507     white_holding[0] = black_holding[0] = NULLCHAR;
9508     ClearProgramStats();
9509     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9510
9511     ResetFrontEnd();
9512     ClearHighlights();
9513     flipView = appData.flipView;
9514     ClearPremoveHighlights();
9515     gotPremove = FALSE;
9516     alarmSounded = FALSE;
9517
9518     GameEnds(EndOfFile, NULL, GE_PLAYER);
9519     if(appData.serverMovesName != NULL) {
9520         /* [HGM] prepare to make moves file for broadcasting */
9521         clock_t t = clock();
9522         if(serverMoves != NULL) fclose(serverMoves);
9523         serverMoves = fopen(appData.serverMovesName, "r");
9524         if(serverMoves != NULL) {
9525             fclose(serverMoves);
9526             /* delay 15 sec before overwriting, so all clients can see end */
9527             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9528         }
9529         serverMoves = fopen(appData.serverMovesName, "w");
9530     }
9531
9532     ExitAnalyzeMode();
9533     gameMode = BeginningOfGame;
9534     ModeHighlight();
9535     if(appData.icsActive) gameInfo.variant = VariantNormal;
9536     currentMove = forwardMostMove = backwardMostMove = 0;
9537     InitPosition(redraw);
9538     for (i = 0; i < MAX_MOVES; i++) {
9539         if (commentList[i] != NULL) {
9540             free(commentList[i]);
9541             commentList[i] = NULL;
9542         }
9543     }
9544     ResetClocks();
9545     timeRemaining[0][0] = whiteTimeRemaining;
9546     timeRemaining[1][0] = blackTimeRemaining;
9547     if (first.pr == NULL) {
9548         StartChessProgram(&first);
9549     }
9550     if (init) {
9551             InitChessProgram(&first, startedFromSetupPosition);
9552     }
9553     DisplayTitle("");
9554     DisplayMessage("", "");
9555     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9556     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9557 }
9558
9559 void
9560 AutoPlayGameLoop()
9561 {
9562     for (;;) {
9563         if (!AutoPlayOneMove())
9564           return;
9565         if (matchMode || appData.timeDelay == 0)
9566           continue;
9567         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9568           return;
9569         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9570         break;
9571     }
9572 }
9573
9574
9575 int
9576 AutoPlayOneMove()
9577 {
9578     int fromX, fromY, toX, toY;
9579
9580     if (appData.debugMode) {
9581       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9582     }
9583
9584     if (gameMode != PlayFromGameFile)
9585       return FALSE;
9586
9587     if (currentMove >= forwardMostMove) {
9588       gameMode = EditGame;
9589       ModeHighlight();
9590
9591       /* [AS] Clear current move marker at the end of a game */
9592       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9593
9594       return FALSE;
9595     }
9596
9597     toX = moveList[currentMove][2] - AAA;
9598     toY = moveList[currentMove][3] - ONE;
9599
9600     if (moveList[currentMove][1] == '@') {
9601         if (appData.highlightLastMove) {
9602             SetHighlights(-1, -1, toX, toY);
9603         }
9604     } else {
9605         fromX = moveList[currentMove][0] - AAA;
9606         fromY = moveList[currentMove][1] - ONE;
9607
9608         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9609
9610         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9611
9612         if (appData.highlightLastMove) {
9613             SetHighlights(fromX, fromY, toX, toY);
9614         }
9615     }
9616     DisplayMove(currentMove);
9617     SendMoveToProgram(currentMove++, &first);
9618     DisplayBothClocks();
9619     DrawPosition(FALSE, boards[currentMove]);
9620     // [HGM] PV info: always display, routine tests if empty
9621     DisplayComment(currentMove - 1, commentList[currentMove]);
9622     return TRUE;
9623 }
9624
9625
9626 int
9627 LoadGameOneMove(readAhead)
9628      ChessMove readAhead;
9629 {
9630     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9631     char promoChar = NULLCHAR;
9632     ChessMove moveType;
9633     char move[MSG_SIZ];
9634     char *p, *q;
9635
9636     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9637         gameMode != AnalyzeMode && gameMode != Training) {
9638         gameFileFP = NULL;
9639         return FALSE;
9640     }
9641
9642     yyboardindex = forwardMostMove;
9643     if (readAhead != EndOfFile) {
9644       moveType = readAhead;
9645     } else {
9646       if (gameFileFP == NULL)
9647           return FALSE;
9648       moveType = (ChessMove) yylex();
9649     }
9650
9651     done = FALSE;
9652     switch (moveType) {
9653       case Comment:
9654         if (appData.debugMode)
9655           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9656         p = yy_text;
9657
9658         /* append the comment but don't display it */
9659         AppendComment(currentMove, p, FALSE);
9660         return TRUE;
9661
9662       case WhiteCapturesEnPassant:
9663       case BlackCapturesEnPassant:
9664       case WhitePromotion:
9665       case BlackPromotion:
9666       case WhiteNonPromotion:
9667       case BlackNonPromotion:
9668       case NormalMove:
9669       case WhiteKingSideCastle:
9670       case WhiteQueenSideCastle:
9671       case BlackKingSideCastle:
9672       case BlackQueenSideCastle:
9673       case WhiteKingSideCastleWild:
9674       case WhiteQueenSideCastleWild:
9675       case BlackKingSideCastleWild:
9676       case BlackQueenSideCastleWild:
9677       /* PUSH Fabien */
9678       case WhiteHSideCastleFR:
9679       case WhiteASideCastleFR:
9680       case BlackHSideCastleFR:
9681       case BlackASideCastleFR:
9682       /* POP Fabien */
9683         if (appData.debugMode)
9684           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9685         fromX = currentMoveString[0] - AAA;
9686         fromY = currentMoveString[1] - ONE;
9687         toX = currentMoveString[2] - AAA;
9688         toY = currentMoveString[3] - ONE;
9689         promoChar = currentMoveString[4];
9690         break;
9691
9692       case WhiteDrop:
9693       case BlackDrop:
9694         if (appData.debugMode)
9695           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9696         fromX = moveType == WhiteDrop ?
9697           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9698         (int) CharToPiece(ToLower(currentMoveString[0]));
9699         fromY = DROP_RANK;
9700         toX = currentMoveString[2] - AAA;
9701         toY = currentMoveString[3] - ONE;
9702         break;
9703
9704       case WhiteWins:
9705       case BlackWins:
9706       case GameIsDrawn:
9707       case GameUnfinished:
9708         if (appData.debugMode)
9709           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9710         p = strchr(yy_text, '{');
9711         if (p == NULL) p = strchr(yy_text, '(');
9712         if (p == NULL) {
9713             p = yy_text;
9714             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9715         } else {
9716             q = strchr(p, *p == '{' ? '}' : ')');
9717             if (q != NULL) *q = NULLCHAR;
9718             p++;
9719         }
9720         GameEnds(moveType, p, GE_FILE);
9721         done = TRUE;
9722         if (cmailMsgLoaded) {
9723             ClearHighlights();
9724             flipView = WhiteOnMove(currentMove);
9725             if (moveType == GameUnfinished) flipView = !flipView;
9726             if (appData.debugMode)
9727               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9728         }
9729         break;
9730
9731       case EndOfFile:
9732         if (appData.debugMode)
9733           fprintf(debugFP, "Parser hit end of file\n");
9734         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9735           case MT_NONE:
9736           case MT_CHECK:
9737             break;
9738           case MT_CHECKMATE:
9739           case MT_STAINMATE:
9740             if (WhiteOnMove(currentMove)) {
9741                 GameEnds(BlackWins, "Black mates", GE_FILE);
9742             } else {
9743                 GameEnds(WhiteWins, "White mates", GE_FILE);
9744             }
9745             break;
9746           case MT_STALEMATE:
9747             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9748             break;
9749         }
9750         done = TRUE;
9751         break;
9752
9753       case MoveNumberOne:
9754         if (lastLoadGameStart == GNUChessGame) {
9755             /* GNUChessGames have numbers, but they aren't move numbers */
9756             if (appData.debugMode)
9757               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9758                       yy_text, (int) moveType);
9759             return LoadGameOneMove(EndOfFile); /* tail recursion */
9760         }
9761         /* else fall thru */
9762
9763       case XBoardGame:
9764       case GNUChessGame:
9765       case PGNTag:
9766         /* Reached start of next game in file */
9767         if (appData.debugMode)
9768           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9769         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9770           case MT_NONE:
9771           case MT_CHECK:
9772             break;
9773           case MT_CHECKMATE:
9774           case MT_STAINMATE:
9775             if (WhiteOnMove(currentMove)) {
9776                 GameEnds(BlackWins, "Black mates", GE_FILE);
9777             } else {
9778                 GameEnds(WhiteWins, "White mates", GE_FILE);
9779             }
9780             break;
9781           case MT_STALEMATE:
9782             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9783             break;
9784         }
9785         done = TRUE;
9786         break;
9787
9788       case PositionDiagram:     /* should not happen; ignore */
9789       case ElapsedTime:         /* ignore */
9790       case NAG:                 /* ignore */
9791         if (appData.debugMode)
9792           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9793                   yy_text, (int) moveType);
9794         return LoadGameOneMove(EndOfFile); /* tail recursion */
9795
9796       case IllegalMove:
9797         if (appData.testLegality) {
9798             if (appData.debugMode)
9799               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9800             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9801                     (forwardMostMove / 2) + 1,
9802                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9803             DisplayError(move, 0);
9804             done = TRUE;
9805         } else {
9806             if (appData.debugMode)
9807               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9808                       yy_text, currentMoveString);
9809             fromX = currentMoveString[0] - AAA;
9810             fromY = currentMoveString[1] - ONE;
9811             toX = currentMoveString[2] - AAA;
9812             toY = currentMoveString[3] - ONE;
9813             promoChar = currentMoveString[4];
9814         }
9815         break;
9816
9817       case AmbiguousMove:
9818         if (appData.debugMode)
9819           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9820         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9821                 (forwardMostMove / 2) + 1,
9822                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9823         DisplayError(move, 0);
9824         done = TRUE;
9825         break;
9826
9827       default:
9828       case ImpossibleMove:
9829         if (appData.debugMode)
9830           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9831         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9832                 (forwardMostMove / 2) + 1,
9833                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9834         DisplayError(move, 0);
9835         done = TRUE;
9836         break;
9837     }
9838
9839     if (done) {
9840         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9841             DrawPosition(FALSE, boards[currentMove]);
9842             DisplayBothClocks();
9843             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9844               DisplayComment(currentMove - 1, commentList[currentMove]);
9845         }
9846         (void) StopLoadGameTimer();
9847         gameFileFP = NULL;
9848         cmailOldMove = forwardMostMove;
9849         return FALSE;
9850     } else {
9851         /* currentMoveString is set as a side-effect of yylex */
9852         strcat(currentMoveString, "\n");
9853         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9854
9855         thinkOutput[0] = NULLCHAR;
9856         MakeMove(fromX, fromY, toX, toY, promoChar);
9857         currentMove = forwardMostMove;
9858         return TRUE;
9859     }
9860 }
9861
9862 /* Load the nth game from the given file */
9863 int
9864 LoadGameFromFile(filename, n, title, useList)
9865      char *filename;
9866      int n;
9867      char *title;
9868      /*Boolean*/ int useList;
9869 {
9870     FILE *f;
9871     char buf[MSG_SIZ];
9872
9873     if (strcmp(filename, "-") == 0) {
9874         f = stdin;
9875         title = "stdin";
9876     } else {
9877         f = fopen(filename, "rb");
9878         if (f == NULL) {
9879           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9880             DisplayError(buf, errno);
9881             return FALSE;
9882         }
9883     }
9884     if (fseek(f, 0, 0) == -1) {
9885         /* f is not seekable; probably a pipe */
9886         useList = FALSE;
9887     }
9888     if (useList && n == 0) {
9889         int error = GameListBuild(f);
9890         if (error) {
9891             DisplayError(_("Cannot build game list"), error);
9892         } else if (!ListEmpty(&gameList) &&
9893                    ((ListGame *) gameList.tailPred)->number > 1) {
9894             GameListPopUp(f, title);
9895             return TRUE;
9896         }
9897         GameListDestroy();
9898         n = 1;
9899     }
9900     if (n == 0) n = 1;
9901     return LoadGame(f, n, title, FALSE);
9902 }
9903
9904
9905 void
9906 MakeRegisteredMove()
9907 {
9908     int fromX, fromY, toX, toY;
9909     char promoChar;
9910     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9911         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9912           case CMAIL_MOVE:
9913           case CMAIL_DRAW:
9914             if (appData.debugMode)
9915               fprintf(debugFP, "Restoring %s for game %d\n",
9916                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9917
9918             thinkOutput[0] = NULLCHAR;
9919             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9920             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9921             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9922             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9923             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9924             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9925             MakeMove(fromX, fromY, toX, toY, promoChar);
9926             ShowMove(fromX, fromY, toX, toY);
9927
9928             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9929               case MT_NONE:
9930               case MT_CHECK:
9931                 break;
9932
9933               case MT_CHECKMATE:
9934               case MT_STAINMATE:
9935                 if (WhiteOnMove(currentMove)) {
9936                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9937                 } else {
9938                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9939                 }
9940                 break;
9941
9942               case MT_STALEMATE:
9943                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9944                 break;
9945             }
9946
9947             break;
9948
9949           case CMAIL_RESIGN:
9950             if (WhiteOnMove(currentMove)) {
9951                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9952             } else {
9953                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9954             }
9955             break;
9956
9957           case CMAIL_ACCEPT:
9958             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9959             break;
9960
9961           default:
9962             break;
9963         }
9964     }
9965
9966     return;
9967 }
9968
9969 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9970 int
9971 CmailLoadGame(f, gameNumber, title, useList)
9972      FILE *f;
9973      int gameNumber;
9974      char *title;
9975      int useList;
9976 {
9977     int retVal;
9978
9979     if (gameNumber > nCmailGames) {
9980         DisplayError(_("No more games in this message"), 0);
9981         return FALSE;
9982     }
9983     if (f == lastLoadGameFP) {
9984         int offset = gameNumber - lastLoadGameNumber;
9985         if (offset == 0) {
9986             cmailMsg[0] = NULLCHAR;
9987             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9988                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9989                 nCmailMovesRegistered--;
9990             }
9991             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9992             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9993                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9994             }
9995         } else {
9996             if (! RegisterMove()) return FALSE;
9997         }
9998     }
9999
10000     retVal = LoadGame(f, gameNumber, title, useList);
10001
10002     /* Make move registered during previous look at this game, if any */
10003     MakeRegisteredMove();
10004
10005     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10006         commentList[currentMove]
10007           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10008         DisplayComment(currentMove - 1, commentList[currentMove]);
10009     }
10010
10011     return retVal;
10012 }
10013
10014 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10015 int
10016 ReloadGame(offset)
10017      int offset;
10018 {
10019     int gameNumber = lastLoadGameNumber + offset;
10020     if (lastLoadGameFP == NULL) {
10021         DisplayError(_("No game has been loaded yet"), 0);
10022         return FALSE;
10023     }
10024     if (gameNumber <= 0) {
10025         DisplayError(_("Can't back up any further"), 0);
10026         return FALSE;
10027     }
10028     if (cmailMsgLoaded) {
10029         return CmailLoadGame(lastLoadGameFP, gameNumber,
10030                              lastLoadGameTitle, lastLoadGameUseList);
10031     } else {
10032         return LoadGame(lastLoadGameFP, gameNumber,
10033                         lastLoadGameTitle, lastLoadGameUseList);
10034     }
10035 }
10036
10037
10038
10039 /* Load the nth game from open file f */
10040 int
10041 LoadGame(f, gameNumber, title, useList)
10042      FILE *f;
10043      int gameNumber;
10044      char *title;
10045      int useList;
10046 {
10047     ChessMove cm;
10048     char buf[MSG_SIZ];
10049     int gn = gameNumber;
10050     ListGame *lg = NULL;
10051     int numPGNTags = 0;
10052     int err;
10053     GameMode oldGameMode;
10054     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10055
10056     if (appData.debugMode)
10057         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10058
10059     if (gameMode == Training )
10060         SetTrainingModeOff();
10061
10062     oldGameMode = gameMode;
10063     if (gameMode != BeginningOfGame) {
10064       Reset(FALSE, TRUE);
10065     }
10066
10067     gameFileFP = f;
10068     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10069         fclose(lastLoadGameFP);
10070     }
10071
10072     if (useList) {
10073         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10074
10075         if (lg) {
10076             fseek(f, lg->offset, 0);
10077             GameListHighlight(gameNumber);
10078             gn = 1;
10079         }
10080         else {
10081             DisplayError(_("Game number out of range"), 0);
10082             return FALSE;
10083         }
10084     } else {
10085         GameListDestroy();
10086         if (fseek(f, 0, 0) == -1) {
10087             if (f == lastLoadGameFP ?
10088                 gameNumber == lastLoadGameNumber + 1 :
10089                 gameNumber == 1) {
10090                 gn = 1;
10091             } else {
10092                 DisplayError(_("Can't seek on game file"), 0);
10093                 return FALSE;
10094             }
10095         }
10096     }
10097     lastLoadGameFP = f;
10098     lastLoadGameNumber = gameNumber;
10099     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10100     lastLoadGameUseList = useList;
10101
10102     yynewfile(f);
10103
10104     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10105       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10106                 lg->gameInfo.black);
10107             DisplayTitle(buf);
10108     } else if (*title != NULLCHAR) {
10109         if (gameNumber > 1) {
10110           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10111             DisplayTitle(buf);
10112         } else {
10113             DisplayTitle(title);
10114         }
10115     }
10116
10117     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10118         gameMode = PlayFromGameFile;
10119         ModeHighlight();
10120     }
10121
10122     currentMove = forwardMostMove = backwardMostMove = 0;
10123     CopyBoard(boards[0], initialPosition);
10124     StopClocks();
10125
10126     /*
10127      * Skip the first gn-1 games in the file.
10128      * Also skip over anything that precedes an identifiable
10129      * start of game marker, to avoid being confused by
10130      * garbage at the start of the file.  Currently
10131      * recognized start of game markers are the move number "1",
10132      * the pattern "gnuchess .* game", the pattern
10133      * "^[#;%] [^ ]* game file", and a PGN tag block.
10134      * A game that starts with one of the latter two patterns
10135      * will also have a move number 1, possibly
10136      * following a position diagram.
10137      * 5-4-02: Let's try being more lenient and allowing a game to
10138      * start with an unnumbered move.  Does that break anything?
10139      */
10140     cm = lastLoadGameStart = EndOfFile;
10141     while (gn > 0) {
10142         yyboardindex = forwardMostMove;
10143         cm = (ChessMove) yylex();
10144         switch (cm) {
10145           case EndOfFile:
10146             if (cmailMsgLoaded) {
10147                 nCmailGames = CMAIL_MAX_GAMES - gn;
10148             } else {
10149                 Reset(TRUE, TRUE);
10150                 DisplayError(_("Game not found in file"), 0);
10151             }
10152             return FALSE;
10153
10154           case GNUChessGame:
10155           case XBoardGame:
10156             gn--;
10157             lastLoadGameStart = cm;
10158             break;
10159
10160           case MoveNumberOne:
10161             switch (lastLoadGameStart) {
10162               case GNUChessGame:
10163               case XBoardGame:
10164               case PGNTag:
10165                 break;
10166               case MoveNumberOne:
10167               case EndOfFile:
10168                 gn--;           /* count this game */
10169                 lastLoadGameStart = cm;
10170                 break;
10171               default:
10172                 /* impossible */
10173                 break;
10174             }
10175             break;
10176
10177           case PGNTag:
10178             switch (lastLoadGameStart) {
10179               case GNUChessGame:
10180               case PGNTag:
10181               case MoveNumberOne:
10182               case EndOfFile:
10183                 gn--;           /* count this game */
10184                 lastLoadGameStart = cm;
10185                 break;
10186               case XBoardGame:
10187                 lastLoadGameStart = cm; /* game counted already */
10188                 break;
10189               default:
10190                 /* impossible */
10191                 break;
10192             }
10193             if (gn > 0) {
10194                 do {
10195                     yyboardindex = forwardMostMove;
10196                     cm = (ChessMove) yylex();
10197                 } while (cm == PGNTag || cm == Comment);
10198             }
10199             break;
10200
10201           case WhiteWins:
10202           case BlackWins:
10203           case GameIsDrawn:
10204             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10205                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10206                     != CMAIL_OLD_RESULT) {
10207                     nCmailResults ++ ;
10208                     cmailResult[  CMAIL_MAX_GAMES
10209                                 - gn - 1] = CMAIL_OLD_RESULT;
10210                 }
10211             }
10212             break;
10213
10214           case NormalMove:
10215             /* Only a NormalMove can be at the start of a game
10216              * without a position diagram. */
10217             if (lastLoadGameStart == EndOfFile ) {
10218               gn--;
10219               lastLoadGameStart = MoveNumberOne;
10220             }
10221             break;
10222
10223           default:
10224             break;
10225         }
10226     }
10227
10228     if (appData.debugMode)
10229       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10230
10231     if (cm == XBoardGame) {
10232         /* Skip any header junk before position diagram and/or move 1 */
10233         for (;;) {
10234             yyboardindex = forwardMostMove;
10235             cm = (ChessMove) yylex();
10236
10237             if (cm == EndOfFile ||
10238                 cm == GNUChessGame || cm == XBoardGame) {
10239                 /* Empty game; pretend end-of-file and handle later */
10240                 cm = EndOfFile;
10241                 break;
10242             }
10243
10244             if (cm == MoveNumberOne || cm == PositionDiagram ||
10245                 cm == PGNTag || cm == Comment)
10246               break;
10247         }
10248     } else if (cm == GNUChessGame) {
10249         if (gameInfo.event != NULL) {
10250             free(gameInfo.event);
10251         }
10252         gameInfo.event = StrSave(yy_text);
10253     }
10254
10255     startedFromSetupPosition = FALSE;
10256     while (cm == PGNTag) {
10257         if (appData.debugMode)
10258           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10259         err = ParsePGNTag(yy_text, &gameInfo);
10260         if (!err) numPGNTags++;
10261
10262         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10263         if(gameInfo.variant != oldVariant) {
10264             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10265             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10266             InitPosition(TRUE);
10267             oldVariant = gameInfo.variant;
10268             if (appData.debugMode)
10269               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10270         }
10271
10272
10273         if (gameInfo.fen != NULL) {
10274           Board initial_position;
10275           startedFromSetupPosition = TRUE;
10276           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10277             Reset(TRUE, TRUE);
10278             DisplayError(_("Bad FEN position in file"), 0);
10279             return FALSE;
10280           }
10281           CopyBoard(boards[0], initial_position);
10282           if (blackPlaysFirst) {
10283             currentMove = forwardMostMove = backwardMostMove = 1;
10284             CopyBoard(boards[1], initial_position);
10285             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10286             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10287             timeRemaining[0][1] = whiteTimeRemaining;
10288             timeRemaining[1][1] = blackTimeRemaining;
10289             if (commentList[0] != NULL) {
10290               commentList[1] = commentList[0];
10291               commentList[0] = NULL;
10292             }
10293           } else {
10294             currentMove = forwardMostMove = backwardMostMove = 0;
10295           }
10296           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10297           {   int i;
10298               initialRulePlies = FENrulePlies;
10299               for( i=0; i< nrCastlingRights; i++ )
10300                   initialRights[i] = initial_position[CASTLING][i];
10301           }
10302           yyboardindex = forwardMostMove;
10303           free(gameInfo.fen);
10304           gameInfo.fen = NULL;
10305         }
10306
10307         yyboardindex = forwardMostMove;
10308         cm = (ChessMove) yylex();
10309
10310         /* Handle comments interspersed among the tags */
10311         while (cm == Comment) {
10312             char *p;
10313             if (appData.debugMode)
10314               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10315             p = yy_text;
10316             AppendComment(currentMove, p, FALSE);
10317             yyboardindex = forwardMostMove;
10318             cm = (ChessMove) yylex();
10319         }
10320     }
10321
10322     /* don't rely on existence of Event tag since if game was
10323      * pasted from clipboard the Event tag may not exist
10324      */
10325     if (numPGNTags > 0){
10326         char *tags;
10327         if (gameInfo.variant == VariantNormal) {
10328           VariantClass v = StringToVariant(gameInfo.event);
10329           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10330           if(v < VariantShogi) gameInfo.variant = v;
10331         }
10332         if (!matchMode) {
10333           if( appData.autoDisplayTags ) {
10334             tags = PGNTags(&gameInfo);
10335             TagsPopUp(tags, CmailMsg());
10336             free(tags);
10337           }
10338         }
10339     } else {
10340         /* Make something up, but don't display it now */
10341         SetGameInfo();
10342         TagsPopDown();
10343     }
10344
10345     if (cm == PositionDiagram) {
10346         int i, j;
10347         char *p;
10348         Board initial_position;
10349
10350         if (appData.debugMode)
10351           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10352
10353         if (!startedFromSetupPosition) {
10354             p = yy_text;
10355             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10356               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10357                 switch (*p) {
10358                   case '[':
10359                   case '-':
10360                   case ' ':
10361                   case '\t':
10362                   case '\n':
10363                   case '\r':
10364                     break;
10365                   default:
10366                     initial_position[i][j++] = CharToPiece(*p);
10367                     break;
10368                 }
10369             while (*p == ' ' || *p == '\t' ||
10370                    *p == '\n' || *p == '\r') p++;
10371
10372             if (strncmp(p, "black", strlen("black"))==0)
10373               blackPlaysFirst = TRUE;
10374             else
10375               blackPlaysFirst = FALSE;
10376             startedFromSetupPosition = TRUE;
10377
10378             CopyBoard(boards[0], initial_position);
10379             if (blackPlaysFirst) {
10380                 currentMove = forwardMostMove = backwardMostMove = 1;
10381                 CopyBoard(boards[1], initial_position);
10382                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10383                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10384                 timeRemaining[0][1] = whiteTimeRemaining;
10385                 timeRemaining[1][1] = blackTimeRemaining;
10386                 if (commentList[0] != NULL) {
10387                     commentList[1] = commentList[0];
10388                     commentList[0] = NULL;
10389                 }
10390             } else {
10391                 currentMove = forwardMostMove = backwardMostMove = 0;
10392             }
10393         }
10394         yyboardindex = forwardMostMove;
10395         cm = (ChessMove) yylex();
10396     }
10397
10398     if (first.pr == NoProc) {
10399         StartChessProgram(&first);
10400     }
10401     InitChessProgram(&first, FALSE);
10402     SendToProgram("force\n", &first);
10403     if (startedFromSetupPosition) {
10404         SendBoard(&first, forwardMostMove);
10405     if (appData.debugMode) {
10406         fprintf(debugFP, "Load Game\n");
10407     }
10408         DisplayBothClocks();
10409     }
10410
10411     /* [HGM] server: flag to write setup moves in broadcast file as one */
10412     loadFlag = appData.suppressLoadMoves;
10413
10414     while (cm == Comment) {
10415         char *p;
10416         if (appData.debugMode)
10417           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10418         p = yy_text;
10419         AppendComment(currentMove, p, FALSE);
10420         yyboardindex = forwardMostMove;
10421         cm = (ChessMove) yylex();
10422     }
10423
10424     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10425         cm == WhiteWins || cm == BlackWins ||
10426         cm == GameIsDrawn || cm == GameUnfinished) {
10427         DisplayMessage("", _("No moves in game"));
10428         if (cmailMsgLoaded) {
10429             if (appData.debugMode)
10430               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10431             ClearHighlights();
10432             flipView = FALSE;
10433         }
10434         DrawPosition(FALSE, boards[currentMove]);
10435         DisplayBothClocks();
10436         gameMode = EditGame;
10437         ModeHighlight();
10438         gameFileFP = NULL;
10439         cmailOldMove = 0;
10440         return TRUE;
10441     }
10442
10443     // [HGM] PV info: routine tests if comment empty
10444     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10445         DisplayComment(currentMove - 1, commentList[currentMove]);
10446     }
10447     if (!matchMode && appData.timeDelay != 0)
10448       DrawPosition(FALSE, boards[currentMove]);
10449
10450     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10451       programStats.ok_to_send = 1;
10452     }
10453
10454     /* if the first token after the PGN tags is a move
10455      * and not move number 1, retrieve it from the parser
10456      */
10457     if (cm != MoveNumberOne)
10458         LoadGameOneMove(cm);
10459
10460     /* load the remaining moves from the file */
10461     while (LoadGameOneMove(EndOfFile)) {
10462       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10463       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10464     }
10465
10466     /* rewind to the start of the game */
10467     currentMove = backwardMostMove;
10468
10469     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10470
10471     if (oldGameMode == AnalyzeFile ||
10472         oldGameMode == AnalyzeMode) {
10473       AnalyzeFileEvent();
10474     }
10475
10476     if (matchMode || appData.timeDelay == 0) {
10477       ToEndEvent();
10478       gameMode = EditGame;
10479       ModeHighlight();
10480     } else if (appData.timeDelay > 0) {
10481       AutoPlayGameLoop();
10482     }
10483
10484     if (appData.debugMode)
10485         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10486
10487     loadFlag = 0; /* [HGM] true game starts */
10488     return TRUE;
10489 }
10490
10491 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10492 int
10493 ReloadPosition(offset)
10494      int offset;
10495 {
10496     int positionNumber = lastLoadPositionNumber + offset;
10497     if (lastLoadPositionFP == NULL) {
10498         DisplayError(_("No position has been loaded yet"), 0);
10499         return FALSE;
10500     }
10501     if (positionNumber <= 0) {
10502         DisplayError(_("Can't back up any further"), 0);
10503         return FALSE;
10504     }
10505     return LoadPosition(lastLoadPositionFP, positionNumber,
10506                         lastLoadPositionTitle);
10507 }
10508
10509 /* Load the nth position from the given file */
10510 int
10511 LoadPositionFromFile(filename, n, title)
10512      char *filename;
10513      int n;
10514      char *title;
10515 {
10516     FILE *f;
10517     char buf[MSG_SIZ];
10518
10519     if (strcmp(filename, "-") == 0) {
10520         return LoadPosition(stdin, n, "stdin");
10521     } else {
10522         f = fopen(filename, "rb");
10523         if (f == NULL) {
10524             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10525             DisplayError(buf, errno);
10526             return FALSE;
10527         } else {
10528             return LoadPosition(f, n, title);
10529         }
10530     }
10531 }
10532
10533 /* Load the nth position from the given open file, and close it */
10534 int
10535 LoadPosition(f, positionNumber, title)
10536      FILE *f;
10537      int positionNumber;
10538      char *title;
10539 {
10540     char *p, line[MSG_SIZ];
10541     Board initial_position;
10542     int i, j, fenMode, pn;
10543
10544     if (gameMode == Training )
10545         SetTrainingModeOff();
10546
10547     if (gameMode != BeginningOfGame) {
10548         Reset(FALSE, TRUE);
10549     }
10550     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10551         fclose(lastLoadPositionFP);
10552     }
10553     if (positionNumber == 0) positionNumber = 1;
10554     lastLoadPositionFP = f;
10555     lastLoadPositionNumber = positionNumber;
10556     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10557     if (first.pr == NoProc) {
10558       StartChessProgram(&first);
10559       InitChessProgram(&first, FALSE);
10560     }
10561     pn = positionNumber;
10562     if (positionNumber < 0) {
10563         /* Negative position number means to seek to that byte offset */
10564         if (fseek(f, -positionNumber, 0) == -1) {
10565             DisplayError(_("Can't seek on position file"), 0);
10566             return FALSE;
10567         };
10568         pn = 1;
10569     } else {
10570         if (fseek(f, 0, 0) == -1) {
10571             if (f == lastLoadPositionFP ?
10572                 positionNumber == lastLoadPositionNumber + 1 :
10573                 positionNumber == 1) {
10574                 pn = 1;
10575             } else {
10576                 DisplayError(_("Can't seek on position file"), 0);
10577                 return FALSE;
10578             }
10579         }
10580     }
10581     /* See if this file is FEN or old-style xboard */
10582     if (fgets(line, MSG_SIZ, f) == NULL) {
10583         DisplayError(_("Position not found in file"), 0);
10584         return FALSE;
10585     }
10586     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10587     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10588
10589     if (pn >= 2) {
10590         if (fenMode || line[0] == '#') pn--;
10591         while (pn > 0) {
10592             /* skip positions before number pn */
10593             if (fgets(line, MSG_SIZ, f) == NULL) {
10594                 Reset(TRUE, TRUE);
10595                 DisplayError(_("Position not found in file"), 0);
10596                 return FALSE;
10597             }
10598             if (fenMode || line[0] == '#') pn--;
10599         }
10600     }
10601
10602     if (fenMode) {
10603         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10604             DisplayError(_("Bad FEN position in file"), 0);
10605             return FALSE;
10606         }
10607     } else {
10608         (void) fgets(line, MSG_SIZ, f);
10609         (void) fgets(line, MSG_SIZ, f);
10610
10611         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10612             (void) fgets(line, MSG_SIZ, f);
10613             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10614                 if (*p == ' ')
10615                   continue;
10616                 initial_position[i][j++] = CharToPiece(*p);
10617             }
10618         }
10619
10620         blackPlaysFirst = FALSE;
10621         if (!feof(f)) {
10622             (void) fgets(line, MSG_SIZ, f);
10623             if (strncmp(line, "black", strlen("black"))==0)
10624               blackPlaysFirst = TRUE;
10625         }
10626     }
10627     startedFromSetupPosition = TRUE;
10628
10629     SendToProgram("force\n", &first);
10630     CopyBoard(boards[0], initial_position);
10631     if (blackPlaysFirst) {
10632         currentMove = forwardMostMove = backwardMostMove = 1;
10633         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10634         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10635         CopyBoard(boards[1], initial_position);
10636         DisplayMessage("", _("Black to play"));
10637     } else {
10638         currentMove = forwardMostMove = backwardMostMove = 0;
10639         DisplayMessage("", _("White to play"));
10640     }
10641     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10642     SendBoard(&first, forwardMostMove);
10643     if (appData.debugMode) {
10644 int i, j;
10645   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10646   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10647         fprintf(debugFP, "Load Position\n");
10648     }
10649
10650     if (positionNumber > 1) {
10651       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10652         DisplayTitle(line);
10653     } else {
10654         DisplayTitle(title);
10655     }
10656     gameMode = EditGame;
10657     ModeHighlight();
10658     ResetClocks();
10659     timeRemaining[0][1] = whiteTimeRemaining;
10660     timeRemaining[1][1] = blackTimeRemaining;
10661     DrawPosition(FALSE, boards[currentMove]);
10662
10663     return TRUE;
10664 }
10665
10666
10667 void
10668 CopyPlayerNameIntoFileName(dest, src)
10669      char **dest, *src;
10670 {
10671     while (*src != NULLCHAR && *src != ',') {
10672         if (*src == ' ') {
10673             *(*dest)++ = '_';
10674             src++;
10675         } else {
10676             *(*dest)++ = *src++;
10677         }
10678     }
10679 }
10680
10681 char *DefaultFileName(ext)
10682      char *ext;
10683 {
10684     static char def[MSG_SIZ];
10685     char *p;
10686
10687     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10688         p = def;
10689         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10690         *p++ = '-';
10691         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10692         *p++ = '.';
10693         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10694     } else {
10695         def[0] = NULLCHAR;
10696     }
10697     return def;
10698 }
10699
10700 /* Save the current game to the given file */
10701 int
10702 SaveGameToFile(filename, append)
10703      char *filename;
10704      int append;
10705 {
10706     FILE *f;
10707     char buf[MSG_SIZ];
10708
10709     if (strcmp(filename, "-") == 0) {
10710         return SaveGame(stdout, 0, NULL);
10711     } else {
10712         f = fopen(filename, append ? "a" : "w");
10713         if (f == NULL) {
10714             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10715             DisplayError(buf, errno);
10716             return FALSE;
10717         } else {
10718             return SaveGame(f, 0, NULL);
10719         }
10720     }
10721 }
10722
10723 char *
10724 SavePart(str)
10725      char *str;
10726 {
10727     static char buf[MSG_SIZ];
10728     char *p;
10729
10730     p = strchr(str, ' ');
10731     if (p == NULL) return str;
10732     strncpy(buf, str, p - str);
10733     buf[p - str] = NULLCHAR;
10734     return buf;
10735 }
10736
10737 #define PGN_MAX_LINE 75
10738
10739 #define PGN_SIDE_WHITE  0
10740 #define PGN_SIDE_BLACK  1
10741
10742 /* [AS] */
10743 static int FindFirstMoveOutOfBook( int side )
10744 {
10745     int result = -1;
10746
10747     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10748         int index = backwardMostMove;
10749         int has_book_hit = 0;
10750
10751         if( (index % 2) != side ) {
10752             index++;
10753         }
10754
10755         while( index < forwardMostMove ) {
10756             /* Check to see if engine is in book */
10757             int depth = pvInfoList[index].depth;
10758             int score = pvInfoList[index].score;
10759             int in_book = 0;
10760
10761             if( depth <= 2 ) {
10762                 in_book = 1;
10763             }
10764             else if( score == 0 && depth == 63 ) {
10765                 in_book = 1; /* Zappa */
10766             }
10767             else if( score == 2 && depth == 99 ) {
10768                 in_book = 1; /* Abrok */
10769             }
10770
10771             has_book_hit += in_book;
10772
10773             if( ! in_book ) {
10774                 result = index;
10775
10776                 break;
10777             }
10778
10779             index += 2;
10780         }
10781     }
10782
10783     return result;
10784 }
10785
10786 /* [AS] */
10787 void GetOutOfBookInfo( char * buf )
10788 {
10789     int oob[2];
10790     int i;
10791     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10792
10793     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10794     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10795
10796     *buf = '\0';
10797
10798     if( oob[0] >= 0 || oob[1] >= 0 ) {
10799         for( i=0; i<2; i++ ) {
10800             int idx = oob[i];
10801
10802             if( idx >= 0 ) {
10803                 if( i > 0 && oob[0] >= 0 ) {
10804                     strcat( buf, "   " );
10805                 }
10806
10807                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10808                 sprintf( buf+strlen(buf), "%s%.2f",
10809                     pvInfoList[idx].score >= 0 ? "+" : "",
10810                     pvInfoList[idx].score / 100.0 );
10811             }
10812         }
10813     }
10814 }
10815
10816 /* Save game in PGN style and close the file */
10817 int
10818 SaveGamePGN(f)
10819      FILE *f;
10820 {
10821     int i, offset, linelen, newblock;
10822     time_t tm;
10823 //    char *movetext;
10824     char numtext[32];
10825     int movelen, numlen, blank;
10826     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10827
10828     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10829
10830     tm = time((time_t *) NULL);
10831
10832     PrintPGNTags(f, &gameInfo);
10833
10834     if (backwardMostMove > 0 || startedFromSetupPosition) {
10835         char *fen = PositionToFEN(backwardMostMove, NULL);
10836         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10837         fprintf(f, "\n{--------------\n");
10838         PrintPosition(f, backwardMostMove);
10839         fprintf(f, "--------------}\n");
10840         free(fen);
10841     }
10842     else {
10843         /* [AS] Out of book annotation */
10844         if( appData.saveOutOfBookInfo ) {
10845             char buf[64];
10846
10847             GetOutOfBookInfo( buf );
10848
10849             if( buf[0] != '\0' ) {
10850                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10851             }
10852         }
10853
10854         fprintf(f, "\n");
10855     }
10856
10857     i = backwardMostMove;
10858     linelen = 0;
10859     newblock = TRUE;
10860
10861     while (i < forwardMostMove) {
10862         /* Print comments preceding this move */
10863         if (commentList[i] != NULL) {
10864             if (linelen > 0) fprintf(f, "\n");
10865             fprintf(f, "%s", commentList[i]);
10866             linelen = 0;
10867             newblock = TRUE;
10868         }
10869
10870         /* Format move number */
10871         if ((i % 2) == 0)
10872           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10873         else
10874           if (newblock)
10875             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10876           else
10877             numtext[0] = NULLCHAR;
10878
10879         numlen = strlen(numtext);
10880         newblock = FALSE;
10881
10882         /* Print move number */
10883         blank = linelen > 0 && numlen > 0;
10884         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10885             fprintf(f, "\n");
10886             linelen = 0;
10887             blank = 0;
10888         }
10889         if (blank) {
10890             fprintf(f, " ");
10891             linelen++;
10892         }
10893         fprintf(f, "%s", numtext);
10894         linelen += numlen;
10895
10896         /* Get move */
10897         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10898         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10899
10900         /* Print move */
10901         blank = linelen > 0 && movelen > 0;
10902         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10903             fprintf(f, "\n");
10904             linelen = 0;
10905             blank = 0;
10906         }
10907         if (blank) {
10908             fprintf(f, " ");
10909             linelen++;
10910         }
10911         fprintf(f, "%s", move_buffer);
10912         linelen += movelen;
10913
10914         /* [AS] Add PV info if present */
10915         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10916             /* [HGM] add time */
10917             char buf[MSG_SIZ]; int seconds;
10918
10919             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10920
10921             if( seconds <= 0)
10922               buf[0] = 0;
10923             else
10924               if( seconds < 30 )
10925                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10926               else
10927                 {
10928                   seconds = (seconds + 4)/10; // round to full seconds
10929                   if( seconds < 60 )
10930                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10931                   else
10932                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10933                 }
10934
10935             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10936                       pvInfoList[i].score >= 0 ? "+" : "",
10937                       pvInfoList[i].score / 100.0,
10938                       pvInfoList[i].depth,
10939                       buf );
10940
10941             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10942
10943             /* Print score/depth */
10944             blank = linelen > 0 && movelen > 0;
10945             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10946                 fprintf(f, "\n");
10947                 linelen = 0;
10948                 blank = 0;
10949             }
10950             if (blank) {
10951                 fprintf(f, " ");
10952                 linelen++;
10953             }
10954             fprintf(f, "%s", move_buffer);
10955             linelen += movelen;
10956         }
10957
10958         i++;
10959     }
10960
10961     /* Start a new line */
10962     if (linelen > 0) fprintf(f, "\n");
10963
10964     /* Print comments after last move */
10965     if (commentList[i] != NULL) {
10966         fprintf(f, "%s\n", commentList[i]);
10967     }
10968
10969     /* Print result */
10970     if (gameInfo.resultDetails != NULL &&
10971         gameInfo.resultDetails[0] != NULLCHAR) {
10972         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10973                 PGNResult(gameInfo.result));
10974     } else {
10975         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10976     }
10977
10978     fclose(f);
10979     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10980     return TRUE;
10981 }
10982
10983 /* Save game in old style and close the file */
10984 int
10985 SaveGameOldStyle(f)
10986      FILE *f;
10987 {
10988     int i, offset;
10989     time_t tm;
10990
10991     tm = time((time_t *) NULL);
10992
10993     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10994     PrintOpponents(f);
10995
10996     if (backwardMostMove > 0 || startedFromSetupPosition) {
10997         fprintf(f, "\n[--------------\n");
10998         PrintPosition(f, backwardMostMove);
10999         fprintf(f, "--------------]\n");
11000     } else {
11001         fprintf(f, "\n");
11002     }
11003
11004     i = backwardMostMove;
11005     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11006
11007     while (i < forwardMostMove) {
11008         if (commentList[i] != NULL) {
11009             fprintf(f, "[%s]\n", commentList[i]);
11010         }
11011
11012         if ((i % 2) == 1) {
11013             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11014             i++;
11015         } else {
11016             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11017             i++;
11018             if (commentList[i] != NULL) {
11019                 fprintf(f, "\n");
11020                 continue;
11021             }
11022             if (i >= forwardMostMove) {
11023                 fprintf(f, "\n");
11024                 break;
11025             }
11026             fprintf(f, "%s\n", parseList[i]);
11027             i++;
11028         }
11029     }
11030
11031     if (commentList[i] != NULL) {
11032         fprintf(f, "[%s]\n", commentList[i]);
11033     }
11034
11035     /* This isn't really the old style, but it's close enough */
11036     if (gameInfo.resultDetails != NULL &&
11037         gameInfo.resultDetails[0] != NULLCHAR) {
11038         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11039                 gameInfo.resultDetails);
11040     } else {
11041         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11042     }
11043
11044     fclose(f);
11045     return TRUE;
11046 }
11047
11048 /* Save the current game to open file f and close the file */
11049 int
11050 SaveGame(f, dummy, dummy2)
11051      FILE *f;
11052      int dummy;
11053      char *dummy2;
11054 {
11055     if (gameMode == EditPosition) EditPositionDone(TRUE);
11056     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11057     if (appData.oldSaveStyle)
11058       return SaveGameOldStyle(f);
11059     else
11060       return SaveGamePGN(f);
11061 }
11062
11063 /* Save the current position to the given file */
11064 int
11065 SavePositionToFile(filename)
11066      char *filename;
11067 {
11068     FILE *f;
11069     char buf[MSG_SIZ];
11070
11071     if (strcmp(filename, "-") == 0) {
11072         return SavePosition(stdout, 0, NULL);
11073     } else {
11074         f = fopen(filename, "a");
11075         if (f == NULL) {
11076             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11077             DisplayError(buf, errno);
11078             return FALSE;
11079         } else {
11080             SavePosition(f, 0, NULL);
11081             return TRUE;
11082         }
11083     }
11084 }
11085
11086 /* Save the current position to the given open file and close the file */
11087 int
11088 SavePosition(f, dummy, dummy2)
11089      FILE *f;
11090      int dummy;
11091      char *dummy2;
11092 {
11093     time_t tm;
11094     char *fen;
11095
11096     if (gameMode == EditPosition) EditPositionDone(TRUE);
11097     if (appData.oldSaveStyle) {
11098         tm = time((time_t *) NULL);
11099
11100         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11101         PrintOpponents(f);
11102         fprintf(f, "[--------------\n");
11103         PrintPosition(f, currentMove);
11104         fprintf(f, "--------------]\n");
11105     } else {
11106         fen = PositionToFEN(currentMove, NULL);
11107         fprintf(f, "%s\n", fen);
11108         free(fen);
11109     }
11110     fclose(f);
11111     return TRUE;
11112 }
11113
11114 void
11115 ReloadCmailMsgEvent(unregister)
11116      int unregister;
11117 {
11118 #if !WIN32
11119     static char *inFilename = NULL;
11120     static char *outFilename;
11121     int i;
11122     struct stat inbuf, outbuf;
11123     int status;
11124
11125     /* Any registered moves are unregistered if unregister is set, */
11126     /* i.e. invoked by the signal handler */
11127     if (unregister) {
11128         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11129             cmailMoveRegistered[i] = FALSE;
11130             if (cmailCommentList[i] != NULL) {
11131                 free(cmailCommentList[i]);
11132                 cmailCommentList[i] = NULL;
11133             }
11134         }
11135         nCmailMovesRegistered = 0;
11136     }
11137
11138     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11139         cmailResult[i] = CMAIL_NOT_RESULT;
11140     }
11141     nCmailResults = 0;
11142
11143     if (inFilename == NULL) {
11144         /* Because the filenames are static they only get malloced once  */
11145         /* and they never get freed                                      */
11146         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11147         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11148
11149         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11150         sprintf(outFilename, "%s.out", appData.cmailGameName);
11151     }
11152
11153     status = stat(outFilename, &outbuf);
11154     if (status < 0) {
11155         cmailMailedMove = FALSE;
11156     } else {
11157         status = stat(inFilename, &inbuf);
11158         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11159     }
11160
11161     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11162        counts the games, notes how each one terminated, etc.
11163
11164        It would be nice to remove this kludge and instead gather all
11165        the information while building the game list.  (And to keep it
11166        in the game list nodes instead of having a bunch of fixed-size
11167        parallel arrays.)  Note this will require getting each game's
11168        termination from the PGN tags, as the game list builder does
11169        not process the game moves.  --mann
11170        */
11171     cmailMsgLoaded = TRUE;
11172     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11173
11174     /* Load first game in the file or popup game menu */
11175     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11176
11177 #endif /* !WIN32 */
11178     return;
11179 }
11180
11181 int
11182 RegisterMove()
11183 {
11184     FILE *f;
11185     char string[MSG_SIZ];
11186
11187     if (   cmailMailedMove
11188         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11189         return TRUE;            /* Allow free viewing  */
11190     }
11191
11192     /* Unregister move to ensure that we don't leave RegisterMove        */
11193     /* with the move registered when the conditions for registering no   */
11194     /* longer hold                                                       */
11195     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11196         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11197         nCmailMovesRegistered --;
11198
11199         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11200           {
11201               free(cmailCommentList[lastLoadGameNumber - 1]);
11202               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11203           }
11204     }
11205
11206     if (cmailOldMove == -1) {
11207         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11208         return FALSE;
11209     }
11210
11211     if (currentMove > cmailOldMove + 1) {
11212         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11213         return FALSE;
11214     }
11215
11216     if (currentMove < cmailOldMove) {
11217         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11218         return FALSE;
11219     }
11220
11221     if (forwardMostMove > currentMove) {
11222         /* Silently truncate extra moves */
11223         TruncateGame();
11224     }
11225
11226     if (   (currentMove == cmailOldMove + 1)
11227         || (   (currentMove == cmailOldMove)
11228             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11229                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11230         if (gameInfo.result != GameUnfinished) {
11231             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11232         }
11233
11234         if (commentList[currentMove] != NULL) {
11235             cmailCommentList[lastLoadGameNumber - 1]
11236               = StrSave(commentList[currentMove]);
11237         }
11238         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11239
11240         if (appData.debugMode)
11241           fprintf(debugFP, "Saving %s for game %d\n",
11242                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11243
11244         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11245
11246         f = fopen(string, "w");
11247         if (appData.oldSaveStyle) {
11248             SaveGameOldStyle(f); /* also closes the file */
11249
11250             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11251             f = fopen(string, "w");
11252             SavePosition(f, 0, NULL); /* also closes the file */
11253         } else {
11254             fprintf(f, "{--------------\n");
11255             PrintPosition(f, currentMove);
11256             fprintf(f, "--------------}\n\n");
11257
11258             SaveGame(f, 0, NULL); /* also closes the file*/
11259         }
11260
11261         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11262         nCmailMovesRegistered ++;
11263     } else if (nCmailGames == 1) {
11264         DisplayError(_("You have not made a move yet"), 0);
11265         return FALSE;
11266     }
11267
11268     return TRUE;
11269 }
11270
11271 void
11272 MailMoveEvent()
11273 {
11274 #if !WIN32
11275     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11276     FILE *commandOutput;
11277     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11278     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11279     int nBuffers;
11280     int i;
11281     int archived;
11282     char *arcDir;
11283
11284     if (! cmailMsgLoaded) {
11285         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11286         return;
11287     }
11288
11289     if (nCmailGames == nCmailResults) {
11290         DisplayError(_("No unfinished games"), 0);
11291         return;
11292     }
11293
11294 #if CMAIL_PROHIBIT_REMAIL
11295     if (cmailMailedMove) {
11296       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);
11297         DisplayError(msg, 0);
11298         return;
11299     }
11300 #endif
11301
11302     if (! (cmailMailedMove || RegisterMove())) return;
11303
11304     if (   cmailMailedMove
11305         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11306       snprintf(string, MSG_SIZ, partCommandString,
11307                appData.debugMode ? " -v" : "", appData.cmailGameName);
11308         commandOutput = popen(string, "r");
11309
11310         if (commandOutput == NULL) {
11311             DisplayError(_("Failed to invoke cmail"), 0);
11312         } else {
11313             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11314                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11315             }
11316             if (nBuffers > 1) {
11317                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11318                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11319                 nBytes = MSG_SIZ - 1;
11320             } else {
11321                 (void) memcpy(msg, buffer, nBytes);
11322             }
11323             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11324
11325             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11326                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11327
11328                 archived = TRUE;
11329                 for (i = 0; i < nCmailGames; i ++) {
11330                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11331                         archived = FALSE;
11332                     }
11333                 }
11334                 if (   archived
11335                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11336                         != NULL)) {
11337                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11338                            arcDir,
11339                            appData.cmailGameName,
11340                            gameInfo.date);
11341                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11342                     cmailMsgLoaded = FALSE;
11343                 }
11344             }
11345
11346             DisplayInformation(msg);
11347             pclose(commandOutput);
11348         }
11349     } else {
11350         if ((*cmailMsg) != '\0') {
11351             DisplayInformation(cmailMsg);
11352         }
11353     }
11354
11355     return;
11356 #endif /* !WIN32 */
11357 }
11358
11359 char *
11360 CmailMsg()
11361 {
11362 #if WIN32
11363     return NULL;
11364 #else
11365     int  prependComma = 0;
11366     char number[5];
11367     char string[MSG_SIZ];       /* Space for game-list */
11368     int  i;
11369
11370     if (!cmailMsgLoaded) return "";
11371
11372     if (cmailMailedMove) {
11373       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11374     } else {
11375         /* Create a list of games left */
11376       snprintf(string, MSG_SIZ, "[");
11377         for (i = 0; i < nCmailGames; i ++) {
11378             if (! (   cmailMoveRegistered[i]
11379                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11380                 if (prependComma) {
11381                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11382                 } else {
11383                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11384                     prependComma = 1;
11385                 }
11386
11387                 strcat(string, number);
11388             }
11389         }
11390         strcat(string, "]");
11391
11392         if (nCmailMovesRegistered + nCmailResults == 0) {
11393             switch (nCmailGames) {
11394               case 1:
11395                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11396                 break;
11397
11398               case 2:
11399                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11400                 break;
11401
11402               default:
11403                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11404                          nCmailGames);
11405                 break;
11406             }
11407         } else {
11408             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11409               case 1:
11410                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11411                          string);
11412                 break;
11413
11414               case 0:
11415                 if (nCmailResults == nCmailGames) {
11416                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11417                 } else {
11418                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11419                 }
11420                 break;
11421
11422               default:
11423                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11424                          string);
11425             }
11426         }
11427     }
11428     return cmailMsg;
11429 #endif /* WIN32 */
11430 }
11431
11432 void
11433 ResetGameEvent()
11434 {
11435     if (gameMode == Training)
11436       SetTrainingModeOff();
11437
11438     Reset(TRUE, TRUE);
11439     cmailMsgLoaded = FALSE;
11440     if (appData.icsActive) {
11441       SendToICS(ics_prefix);
11442       SendToICS("refresh\n");
11443     }
11444 }
11445
11446 void
11447 ExitEvent(status)
11448      int status;
11449 {
11450     exiting++;
11451     if (exiting > 2) {
11452       /* Give up on clean exit */
11453       exit(status);
11454     }
11455     if (exiting > 1) {
11456       /* Keep trying for clean exit */
11457       return;
11458     }
11459
11460     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11461
11462     if (telnetISR != NULL) {
11463       RemoveInputSource(telnetISR);
11464     }
11465     if (icsPR != NoProc) {
11466       DestroyChildProcess(icsPR, TRUE);
11467     }
11468
11469     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11470     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11471
11472     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11473     /* make sure this other one finishes before killing it!                  */
11474     if(endingGame) { int count = 0;
11475         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11476         while(endingGame && count++ < 10) DoSleep(1);
11477         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11478     }
11479
11480     /* Kill off chess programs */
11481     if (first.pr != NoProc) {
11482         ExitAnalyzeMode();
11483
11484         DoSleep( appData.delayBeforeQuit );
11485         SendToProgram("quit\n", &first);
11486         DoSleep( appData.delayAfterQuit );
11487         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11488     }
11489     if (second.pr != NoProc) {
11490         DoSleep( appData.delayBeforeQuit );
11491         SendToProgram("quit\n", &second);
11492         DoSleep( appData.delayAfterQuit );
11493         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11494     }
11495     if (first.isr != NULL) {
11496         RemoveInputSource(first.isr);
11497     }
11498     if (second.isr != NULL) {
11499         RemoveInputSource(second.isr);
11500     }
11501
11502     ShutDownFrontEnd();
11503     exit(status);
11504 }
11505
11506 void
11507 PauseEvent()
11508 {
11509     if (appData.debugMode)
11510         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11511     if (pausing) {
11512         pausing = FALSE;
11513         ModeHighlight();
11514         if (gameMode == MachinePlaysWhite ||
11515             gameMode == MachinePlaysBlack) {
11516             StartClocks();
11517         } else {
11518             DisplayBothClocks();
11519         }
11520         if (gameMode == PlayFromGameFile) {
11521             if (appData.timeDelay >= 0)
11522                 AutoPlayGameLoop();
11523         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11524             Reset(FALSE, TRUE);
11525             SendToICS(ics_prefix);
11526             SendToICS("refresh\n");
11527         } else if (currentMove < forwardMostMove) {
11528             ForwardInner(forwardMostMove);
11529         }
11530         pauseExamInvalid = FALSE;
11531     } else {
11532         switch (gameMode) {
11533           default:
11534             return;
11535           case IcsExamining:
11536             pauseExamForwardMostMove = forwardMostMove;
11537             pauseExamInvalid = FALSE;
11538             /* fall through */
11539           case IcsObserving:
11540           case IcsPlayingWhite:
11541           case IcsPlayingBlack:
11542             pausing = TRUE;
11543             ModeHighlight();
11544             return;
11545           case PlayFromGameFile:
11546             (void) StopLoadGameTimer();
11547             pausing = TRUE;
11548             ModeHighlight();
11549             break;
11550           case BeginningOfGame:
11551             if (appData.icsActive) return;
11552             /* else fall through */
11553           case MachinePlaysWhite:
11554           case MachinePlaysBlack:
11555           case TwoMachinesPlay:
11556             if (forwardMostMove == 0)
11557               return;           /* don't pause if no one has moved */
11558             if ((gameMode == MachinePlaysWhite &&
11559                  !WhiteOnMove(forwardMostMove)) ||
11560                 (gameMode == MachinePlaysBlack &&
11561                  WhiteOnMove(forwardMostMove))) {
11562                 StopClocks();
11563             }
11564             pausing = TRUE;
11565             ModeHighlight();
11566             break;
11567         }
11568     }
11569 }
11570
11571 void
11572 EditCommentEvent()
11573 {
11574     char title[MSG_SIZ];
11575
11576     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11577       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11578     } else {
11579       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11580                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11581                parseList[currentMove - 1]);
11582     }
11583
11584     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11585 }
11586
11587
11588 void
11589 EditTagsEvent()
11590 {
11591     char *tags = PGNTags(&gameInfo);
11592     EditTagsPopUp(tags);
11593     free(tags);
11594 }
11595
11596 void
11597 AnalyzeModeEvent()
11598 {
11599     if (appData.noChessProgram || gameMode == AnalyzeMode)
11600       return;
11601
11602     if (gameMode != AnalyzeFile) {
11603         if (!appData.icsEngineAnalyze) {
11604                EditGameEvent();
11605                if (gameMode != EditGame) return;
11606         }
11607         ResurrectChessProgram();
11608         SendToProgram("analyze\n", &first);
11609         first.analyzing = TRUE;
11610         /*first.maybeThinking = TRUE;*/
11611         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11612         EngineOutputPopUp();
11613     }
11614     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11615     pausing = FALSE;
11616     ModeHighlight();
11617     SetGameInfo();
11618
11619     StartAnalysisClock();
11620     GetTimeMark(&lastNodeCountTime);
11621     lastNodeCount = 0;
11622 }
11623
11624 void
11625 AnalyzeFileEvent()
11626 {
11627     if (appData.noChessProgram || gameMode == AnalyzeFile)
11628       return;
11629
11630     if (gameMode != AnalyzeMode) {
11631         EditGameEvent();
11632         if (gameMode != EditGame) return;
11633         ResurrectChessProgram();
11634         SendToProgram("analyze\n", &first);
11635         first.analyzing = TRUE;
11636         /*first.maybeThinking = TRUE;*/
11637         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11638         EngineOutputPopUp();
11639     }
11640     gameMode = AnalyzeFile;
11641     pausing = FALSE;
11642     ModeHighlight();
11643     SetGameInfo();
11644
11645     StartAnalysisClock();
11646     GetTimeMark(&lastNodeCountTime);
11647     lastNodeCount = 0;
11648 }
11649
11650 void
11651 MachineWhiteEvent()
11652 {
11653     char buf[MSG_SIZ];
11654     char *bookHit = NULL;
11655
11656     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11657       return;
11658
11659
11660     if (gameMode == PlayFromGameFile ||
11661         gameMode == TwoMachinesPlay  ||
11662         gameMode == Training         ||
11663         gameMode == AnalyzeMode      ||
11664         gameMode == EndOfGame)
11665         EditGameEvent();
11666
11667     if (gameMode == EditPosition)
11668         EditPositionDone(TRUE);
11669
11670     if (!WhiteOnMove(currentMove)) {
11671         DisplayError(_("It is not White's turn"), 0);
11672         return;
11673     }
11674
11675     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11676       ExitAnalyzeMode();
11677
11678     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11679         gameMode == AnalyzeFile)
11680         TruncateGame();
11681
11682     ResurrectChessProgram();    /* in case it isn't running */
11683     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11684         gameMode = MachinePlaysWhite;
11685         ResetClocks();
11686     } else
11687     gameMode = MachinePlaysWhite;
11688     pausing = FALSE;
11689     ModeHighlight();
11690     SetGameInfo();
11691     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11692     DisplayTitle(buf);
11693     if (first.sendName) {
11694       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11695       SendToProgram(buf, &first);
11696     }
11697     if (first.sendTime) {
11698       if (first.useColors) {
11699         SendToProgram("black\n", &first); /*gnu kludge*/
11700       }
11701       SendTimeRemaining(&first, TRUE);
11702     }
11703     if (first.useColors) {
11704       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11705     }
11706     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11707     SetMachineThinkingEnables();
11708     first.maybeThinking = TRUE;
11709     StartClocks();
11710     firstMove = FALSE;
11711
11712     if (appData.autoFlipView && !flipView) {
11713       flipView = !flipView;
11714       DrawPosition(FALSE, NULL);
11715       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11716     }
11717
11718     if(bookHit) { // [HGM] book: simulate book reply
11719         static char bookMove[MSG_SIZ]; // a bit generous?
11720
11721         programStats.nodes = programStats.depth = programStats.time =
11722         programStats.score = programStats.got_only_move = 0;
11723         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11724
11725         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11726         strcat(bookMove, bookHit);
11727         HandleMachineMove(bookMove, &first);
11728     }
11729 }
11730
11731 void
11732 MachineBlackEvent()
11733 {
11734   char buf[MSG_SIZ];
11735   char *bookHit = NULL;
11736
11737     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11738         return;
11739
11740
11741     if (gameMode == PlayFromGameFile ||
11742         gameMode == TwoMachinesPlay  ||
11743         gameMode == Training         ||
11744         gameMode == AnalyzeMode      ||
11745         gameMode == EndOfGame)
11746         EditGameEvent();
11747
11748     if (gameMode == EditPosition)
11749         EditPositionDone(TRUE);
11750
11751     if (WhiteOnMove(currentMove)) {
11752         DisplayError(_("It is not Black's turn"), 0);
11753         return;
11754     }
11755
11756     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11757       ExitAnalyzeMode();
11758
11759     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11760         gameMode == AnalyzeFile)
11761         TruncateGame();
11762
11763     ResurrectChessProgram();    /* in case it isn't running */
11764     gameMode = MachinePlaysBlack;
11765     pausing = FALSE;
11766     ModeHighlight();
11767     SetGameInfo();
11768     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11769     DisplayTitle(buf);
11770     if (first.sendName) {
11771       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11772       SendToProgram(buf, &first);
11773     }
11774     if (first.sendTime) {
11775       if (first.useColors) {
11776         SendToProgram("white\n", &first); /*gnu kludge*/
11777       }
11778       SendTimeRemaining(&first, FALSE);
11779     }
11780     if (first.useColors) {
11781       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11782     }
11783     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11784     SetMachineThinkingEnables();
11785     first.maybeThinking = TRUE;
11786     StartClocks();
11787
11788     if (appData.autoFlipView && flipView) {
11789       flipView = !flipView;
11790       DrawPosition(FALSE, NULL);
11791       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11792     }
11793     if(bookHit) { // [HGM] book: simulate book reply
11794         static char bookMove[MSG_SIZ]; // a bit generous?
11795
11796         programStats.nodes = programStats.depth = programStats.time =
11797         programStats.score = programStats.got_only_move = 0;
11798         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11799
11800         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11801         strcat(bookMove, bookHit);
11802         HandleMachineMove(bookMove, &first);
11803     }
11804 }
11805
11806
11807 void
11808 DisplayTwoMachinesTitle()
11809 {
11810     char buf[MSG_SIZ];
11811     if (appData.matchGames > 0) {
11812         if (first.twoMachinesColor[0] == 'w') {
11813           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11814                    gameInfo.white, gameInfo.black,
11815                    first.matchWins, second.matchWins,
11816                    matchGame - 1 - (first.matchWins + second.matchWins));
11817         } else {
11818           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11819                    gameInfo.white, gameInfo.black,
11820                    second.matchWins, first.matchWins,
11821                    matchGame - 1 - (first.matchWins + second.matchWins));
11822         }
11823     } else {
11824       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11825     }
11826     DisplayTitle(buf);
11827 }
11828
11829 void
11830 TwoMachinesEvent P((void))
11831 {
11832     int i;
11833     char buf[MSG_SIZ];
11834     ChessProgramState *onmove;
11835     char *bookHit = NULL;
11836
11837     if (appData.noChessProgram) return;
11838
11839     switch (gameMode) {
11840       case TwoMachinesPlay:
11841         return;
11842       case MachinePlaysWhite:
11843       case MachinePlaysBlack:
11844         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11845             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11846             return;
11847         }
11848         /* fall through */
11849       case BeginningOfGame:
11850       case PlayFromGameFile:
11851       case EndOfGame:
11852         EditGameEvent();
11853         if (gameMode != EditGame) return;
11854         break;
11855       case EditPosition:
11856         EditPositionDone(TRUE);
11857         break;
11858       case AnalyzeMode:
11859       case AnalyzeFile:
11860         ExitAnalyzeMode();
11861         break;
11862       case EditGame:
11863       default:
11864         break;
11865     }
11866
11867 //    forwardMostMove = currentMove;
11868     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11869     ResurrectChessProgram();    /* in case first program isn't running */
11870
11871     if (second.pr == NULL) {
11872         StartChessProgram(&second);
11873         if (second.protocolVersion == 1) {
11874           TwoMachinesEventIfReady();
11875         } else {
11876           /* kludge: allow timeout for initial "feature" command */
11877           FreezeUI();
11878           DisplayMessage("", _("Starting second chess program"));
11879           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11880         }
11881         return;
11882     }
11883     DisplayMessage("", "");
11884     InitChessProgram(&second, FALSE);
11885     SendToProgram("force\n", &second);
11886     if (startedFromSetupPosition) {
11887         SendBoard(&second, backwardMostMove);
11888     if (appData.debugMode) {
11889         fprintf(debugFP, "Two Machines\n");
11890     }
11891     }
11892     for (i = backwardMostMove; i < forwardMostMove; i++) {
11893         SendMoveToProgram(i, &second);
11894     }
11895
11896     gameMode = TwoMachinesPlay;
11897     pausing = FALSE;
11898     ModeHighlight();
11899     SetGameInfo();
11900     DisplayTwoMachinesTitle();
11901     firstMove = TRUE;
11902     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11903         onmove = &first;
11904     } else {
11905         onmove = &second;
11906     }
11907
11908     SendToProgram(first.computerString, &first);
11909     if (first.sendName) {
11910       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11911       SendToProgram(buf, &first);
11912     }
11913     SendToProgram(second.computerString, &second);
11914     if (second.sendName) {
11915       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11916       SendToProgram(buf, &second);
11917     }
11918
11919     ResetClocks();
11920     if (!first.sendTime || !second.sendTime) {
11921         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11922         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11923     }
11924     if (onmove->sendTime) {
11925       if (onmove->useColors) {
11926         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11927       }
11928       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11929     }
11930     if (onmove->useColors) {
11931       SendToProgram(onmove->twoMachinesColor, onmove);
11932     }
11933     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11934 //    SendToProgram("go\n", onmove);
11935     onmove->maybeThinking = TRUE;
11936     SetMachineThinkingEnables();
11937
11938     StartClocks();
11939
11940     if(bookHit) { // [HGM] book: simulate book reply
11941         static char bookMove[MSG_SIZ]; // a bit generous?
11942
11943         programStats.nodes = programStats.depth = programStats.time =
11944         programStats.score = programStats.got_only_move = 0;
11945         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11946
11947         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11948         strcat(bookMove, bookHit);
11949         savedMessage = bookMove; // args for deferred call
11950         savedState = onmove;
11951         ScheduleDelayedEvent(DeferredBookMove, 1);
11952     }
11953 }
11954
11955 void
11956 TrainingEvent()
11957 {
11958     if (gameMode == Training) {
11959       SetTrainingModeOff();
11960       gameMode = PlayFromGameFile;
11961       DisplayMessage("", _("Training mode off"));
11962     } else {
11963       gameMode = Training;
11964       animateTraining = appData.animate;
11965
11966       /* make sure we are not already at the end of the game */
11967       if (currentMove < forwardMostMove) {
11968         SetTrainingModeOn();
11969         DisplayMessage("", _("Training mode on"));
11970       } else {
11971         gameMode = PlayFromGameFile;
11972         DisplayError(_("Already at end of game"), 0);
11973       }
11974     }
11975     ModeHighlight();
11976 }
11977
11978 void
11979 IcsClientEvent()
11980 {
11981     if (!appData.icsActive) return;
11982     switch (gameMode) {
11983       case IcsPlayingWhite:
11984       case IcsPlayingBlack:
11985       case IcsObserving:
11986       case IcsIdle:
11987       case BeginningOfGame:
11988       case IcsExamining:
11989         return;
11990
11991       case EditGame:
11992         break;
11993
11994       case EditPosition:
11995         EditPositionDone(TRUE);
11996         break;
11997
11998       case AnalyzeMode:
11999       case AnalyzeFile:
12000         ExitAnalyzeMode();
12001         break;
12002
12003       default:
12004         EditGameEvent();
12005         break;
12006     }
12007
12008     gameMode = IcsIdle;
12009     ModeHighlight();
12010     return;
12011 }
12012
12013
12014 void
12015 EditGameEvent()
12016 {
12017     int i;
12018
12019     switch (gameMode) {
12020       case Training:
12021         SetTrainingModeOff();
12022         break;
12023       case MachinePlaysWhite:
12024       case MachinePlaysBlack:
12025       case BeginningOfGame:
12026         SendToProgram("force\n", &first);
12027         SetUserThinkingEnables();
12028         break;
12029       case PlayFromGameFile:
12030         (void) StopLoadGameTimer();
12031         if (gameFileFP != NULL) {
12032             gameFileFP = NULL;
12033         }
12034         break;
12035       case EditPosition:
12036         EditPositionDone(TRUE);
12037         break;
12038       case AnalyzeMode:
12039       case AnalyzeFile:
12040         ExitAnalyzeMode();
12041         SendToProgram("force\n", &first);
12042         break;
12043       case TwoMachinesPlay:
12044         GameEnds(EndOfFile, NULL, GE_PLAYER);
12045         ResurrectChessProgram();
12046         SetUserThinkingEnables();
12047         break;
12048       case EndOfGame:
12049         ResurrectChessProgram();
12050         break;
12051       case IcsPlayingBlack:
12052       case IcsPlayingWhite:
12053         DisplayError(_("Warning: You are still playing a game"), 0);
12054         break;
12055       case IcsObserving:
12056         DisplayError(_("Warning: You are still observing a game"), 0);
12057         break;
12058       case IcsExamining:
12059         DisplayError(_("Warning: You are still examining a game"), 0);
12060         break;
12061       case IcsIdle:
12062         break;
12063       case EditGame:
12064       default:
12065         return;
12066     }
12067
12068     pausing = FALSE;
12069     StopClocks();
12070     first.offeredDraw = second.offeredDraw = 0;
12071
12072     if (gameMode == PlayFromGameFile) {
12073         whiteTimeRemaining = timeRemaining[0][currentMove];
12074         blackTimeRemaining = timeRemaining[1][currentMove];
12075         DisplayTitle("");
12076     }
12077
12078     if (gameMode == MachinePlaysWhite ||
12079         gameMode == MachinePlaysBlack ||
12080         gameMode == TwoMachinesPlay ||
12081         gameMode == EndOfGame) {
12082         i = forwardMostMove;
12083         while (i > currentMove) {
12084             SendToProgram("undo\n", &first);
12085             i--;
12086         }
12087         whiteTimeRemaining = timeRemaining[0][currentMove];
12088         blackTimeRemaining = timeRemaining[1][currentMove];
12089         DisplayBothClocks();
12090         if (whiteFlag || blackFlag) {
12091             whiteFlag = blackFlag = 0;
12092         }
12093         DisplayTitle("");
12094     }
12095
12096     gameMode = EditGame;
12097     ModeHighlight();
12098     SetGameInfo();
12099 }
12100
12101
12102 void
12103 EditPositionEvent()
12104 {
12105     if (gameMode == EditPosition) {
12106         EditGameEvent();
12107         return;
12108     }
12109
12110     EditGameEvent();
12111     if (gameMode != EditGame) return;
12112
12113     gameMode = EditPosition;
12114     ModeHighlight();
12115     SetGameInfo();
12116     if (currentMove > 0)
12117       CopyBoard(boards[0], boards[currentMove]);
12118
12119     blackPlaysFirst = !WhiteOnMove(currentMove);
12120     ResetClocks();
12121     currentMove = forwardMostMove = backwardMostMove = 0;
12122     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12123     DisplayMove(-1);
12124 }
12125
12126 void
12127 ExitAnalyzeMode()
12128 {
12129     /* [DM] icsEngineAnalyze - possible call from other functions */
12130     if (appData.icsEngineAnalyze) {
12131         appData.icsEngineAnalyze = FALSE;
12132
12133         DisplayMessage("",_("Close ICS engine analyze..."));
12134     }
12135     if (first.analysisSupport && first.analyzing) {
12136       SendToProgram("exit\n", &first);
12137       first.analyzing = FALSE;
12138     }
12139     thinkOutput[0] = NULLCHAR;
12140 }
12141
12142 void
12143 EditPositionDone(Boolean fakeRights)
12144 {
12145     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12146
12147     startedFromSetupPosition = TRUE;
12148     InitChessProgram(&first, FALSE);
12149     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12150       boards[0][EP_STATUS] = EP_NONE;
12151       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12152     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12153         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12154         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12155       } else boards[0][CASTLING][2] = NoRights;
12156     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12157         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12158         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12159       } else boards[0][CASTLING][5] = NoRights;
12160     }
12161     SendToProgram("force\n", &first);
12162     if (blackPlaysFirst) {
12163         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12164         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12165         currentMove = forwardMostMove = backwardMostMove = 1;
12166         CopyBoard(boards[1], boards[0]);
12167     } else {
12168         currentMove = forwardMostMove = backwardMostMove = 0;
12169     }
12170     SendBoard(&first, forwardMostMove);
12171     if (appData.debugMode) {
12172         fprintf(debugFP, "EditPosDone\n");
12173     }
12174     DisplayTitle("");
12175     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12176     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12177     gameMode = EditGame;
12178     ModeHighlight();
12179     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12180     ClearHighlights(); /* [AS] */
12181 }
12182
12183 /* Pause for `ms' milliseconds */
12184 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12185 void
12186 TimeDelay(ms)
12187      long ms;
12188 {
12189     TimeMark m1, m2;
12190
12191     GetTimeMark(&m1);
12192     do {
12193         GetTimeMark(&m2);
12194     } while (SubtractTimeMarks(&m2, &m1) < ms);
12195 }
12196
12197 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12198 void
12199 SendMultiLineToICS(buf)
12200      char *buf;
12201 {
12202     char temp[MSG_SIZ+1], *p;
12203     int len;
12204
12205     len = strlen(buf);
12206     if (len > MSG_SIZ)
12207       len = MSG_SIZ;
12208
12209     strncpy(temp, buf, len);
12210     temp[len] = 0;
12211
12212     p = temp;
12213     while (*p) {
12214         if (*p == '\n' || *p == '\r')
12215           *p = ' ';
12216         ++p;
12217     }
12218
12219     strcat(temp, "\n");
12220     SendToICS(temp);
12221     SendToPlayer(temp, strlen(temp));
12222 }
12223
12224 void
12225 SetWhiteToPlayEvent()
12226 {
12227     if (gameMode == EditPosition) {
12228         blackPlaysFirst = FALSE;
12229         DisplayBothClocks();    /* works because currentMove is 0 */
12230     } else if (gameMode == IcsExamining) {
12231         SendToICS(ics_prefix);
12232         SendToICS("tomove white\n");
12233     }
12234 }
12235
12236 void
12237 SetBlackToPlayEvent()
12238 {
12239     if (gameMode == EditPosition) {
12240         blackPlaysFirst = TRUE;
12241         currentMove = 1;        /* kludge */
12242         DisplayBothClocks();
12243         currentMove = 0;
12244     } else if (gameMode == IcsExamining) {
12245         SendToICS(ics_prefix);
12246         SendToICS("tomove black\n");
12247     }
12248 }
12249
12250 void
12251 EditPositionMenuEvent(selection, x, y)
12252      ChessSquare selection;
12253      int x, y;
12254 {
12255     char buf[MSG_SIZ];
12256     ChessSquare piece = boards[0][y][x];
12257
12258     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12259
12260     switch (selection) {
12261       case ClearBoard:
12262         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12263             SendToICS(ics_prefix);
12264             SendToICS("bsetup clear\n");
12265         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12266             SendToICS(ics_prefix);
12267             SendToICS("clearboard\n");
12268         } else {
12269             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12270                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12271                 for (y = 0; y < BOARD_HEIGHT; y++) {
12272                     if (gameMode == IcsExamining) {
12273                         if (boards[currentMove][y][x] != EmptySquare) {
12274                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12275                                     AAA + x, ONE + y);
12276                             SendToICS(buf);
12277                         }
12278                     } else {
12279                         boards[0][y][x] = p;
12280                     }
12281                 }
12282             }
12283         }
12284         if (gameMode == EditPosition) {
12285             DrawPosition(FALSE, boards[0]);
12286         }
12287         break;
12288
12289       case WhitePlay:
12290         SetWhiteToPlayEvent();
12291         break;
12292
12293       case BlackPlay:
12294         SetBlackToPlayEvent();
12295         break;
12296
12297       case EmptySquare:
12298         if (gameMode == IcsExamining) {
12299             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12300             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12301             SendToICS(buf);
12302         } else {
12303             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12304                 if(x == BOARD_LEFT-2) {
12305                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12306                     boards[0][y][1] = 0;
12307                 } else
12308                 if(x == BOARD_RGHT+1) {
12309                     if(y >= gameInfo.holdingsSize) break;
12310                     boards[0][y][BOARD_WIDTH-2] = 0;
12311                 } else break;
12312             }
12313             boards[0][y][x] = EmptySquare;
12314             DrawPosition(FALSE, boards[0]);
12315         }
12316         break;
12317
12318       case PromotePiece:
12319         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12320            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12321             selection = (ChessSquare) (PROMOTED piece);
12322         } else if(piece == EmptySquare) selection = WhiteSilver;
12323         else selection = (ChessSquare)((int)piece - 1);
12324         goto defaultlabel;
12325
12326       case DemotePiece:
12327         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12328            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12329             selection = (ChessSquare) (DEMOTED piece);
12330         } else if(piece == EmptySquare) selection = BlackSilver;
12331         else selection = (ChessSquare)((int)piece + 1);
12332         goto defaultlabel;
12333
12334       case WhiteQueen:
12335       case BlackQueen:
12336         if(gameInfo.variant == VariantShatranj ||
12337            gameInfo.variant == VariantXiangqi  ||
12338            gameInfo.variant == VariantCourier  ||
12339            gameInfo.variant == VariantMakruk     )
12340             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12341         goto defaultlabel;
12342
12343       case WhiteKing:
12344       case BlackKing:
12345         if(gameInfo.variant == VariantXiangqi)
12346             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12347         if(gameInfo.variant == VariantKnightmate)
12348             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12349       default:
12350         defaultlabel:
12351         if (gameMode == IcsExamining) {
12352             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12353             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12354                      PieceToChar(selection), AAA + x, ONE + y);
12355             SendToICS(buf);
12356         } else {
12357             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12358                 int n;
12359                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12360                     n = PieceToNumber(selection - BlackPawn);
12361                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12362                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12363                     boards[0][BOARD_HEIGHT-1-n][1]++;
12364                 } else
12365                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12366                     n = PieceToNumber(selection);
12367                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12368                     boards[0][n][BOARD_WIDTH-1] = selection;
12369                     boards[0][n][BOARD_WIDTH-2]++;
12370                 }
12371             } else
12372             boards[0][y][x] = selection;
12373             DrawPosition(TRUE, boards[0]);
12374         }
12375         break;
12376     }
12377 }
12378
12379
12380 void
12381 DropMenuEvent(selection, x, y)
12382      ChessSquare selection;
12383      int x, y;
12384 {
12385     ChessMove moveType;
12386
12387     switch (gameMode) {
12388       case IcsPlayingWhite:
12389       case MachinePlaysBlack:
12390         if (!WhiteOnMove(currentMove)) {
12391             DisplayMoveError(_("It is Black's turn"));
12392             return;
12393         }
12394         moveType = WhiteDrop;
12395         break;
12396       case IcsPlayingBlack:
12397       case MachinePlaysWhite:
12398         if (WhiteOnMove(currentMove)) {
12399             DisplayMoveError(_("It is White's turn"));
12400             return;
12401         }
12402         moveType = BlackDrop;
12403         break;
12404       case EditGame:
12405         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12406         break;
12407       default:
12408         return;
12409     }
12410
12411     if (moveType == BlackDrop && selection < BlackPawn) {
12412       selection = (ChessSquare) ((int) selection
12413                                  + (int) BlackPawn - (int) WhitePawn);
12414     }
12415     if (boards[currentMove][y][x] != EmptySquare) {
12416         DisplayMoveError(_("That square is occupied"));
12417         return;
12418     }
12419
12420     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12421 }
12422
12423 void
12424 AcceptEvent()
12425 {
12426     /* Accept a pending offer of any kind from opponent */
12427
12428     if (appData.icsActive) {
12429         SendToICS(ics_prefix);
12430         SendToICS("accept\n");
12431     } else if (cmailMsgLoaded) {
12432         if (currentMove == cmailOldMove &&
12433             commentList[cmailOldMove] != NULL &&
12434             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12435                    "Black offers a draw" : "White offers a draw")) {
12436             TruncateGame();
12437             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12438             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12439         } else {
12440             DisplayError(_("There is no pending offer on this move"), 0);
12441             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12442         }
12443     } else {
12444         /* Not used for offers from chess program */
12445     }
12446 }
12447
12448 void
12449 DeclineEvent()
12450 {
12451     /* Decline a pending offer of any kind from opponent */
12452
12453     if (appData.icsActive) {
12454         SendToICS(ics_prefix);
12455         SendToICS("decline\n");
12456     } else if (cmailMsgLoaded) {
12457         if (currentMove == cmailOldMove &&
12458             commentList[cmailOldMove] != NULL &&
12459             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12460                    "Black offers a draw" : "White offers a draw")) {
12461 #ifdef NOTDEF
12462             AppendComment(cmailOldMove, "Draw declined", TRUE);
12463             DisplayComment(cmailOldMove - 1, "Draw declined");
12464 #endif /*NOTDEF*/
12465         } else {
12466             DisplayError(_("There is no pending offer on this move"), 0);
12467         }
12468     } else {
12469         /* Not used for offers from chess program */
12470     }
12471 }
12472
12473 void
12474 RematchEvent()
12475 {
12476     /* Issue ICS rematch command */
12477     if (appData.icsActive) {
12478         SendToICS(ics_prefix);
12479         SendToICS("rematch\n");
12480     }
12481 }
12482
12483 void
12484 CallFlagEvent()
12485 {
12486     /* Call your opponent's flag (claim a win on time) */
12487     if (appData.icsActive) {
12488         SendToICS(ics_prefix);
12489         SendToICS("flag\n");
12490     } else {
12491         switch (gameMode) {
12492           default:
12493             return;
12494           case MachinePlaysWhite:
12495             if (whiteFlag) {
12496                 if (blackFlag)
12497                   GameEnds(GameIsDrawn, "Both players ran out of time",
12498                            GE_PLAYER);
12499                 else
12500                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12501             } else {
12502                 DisplayError(_("Your opponent is not out of time"), 0);
12503             }
12504             break;
12505           case MachinePlaysBlack:
12506             if (blackFlag) {
12507                 if (whiteFlag)
12508                   GameEnds(GameIsDrawn, "Both players ran out of time",
12509                            GE_PLAYER);
12510                 else
12511                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12512             } else {
12513                 DisplayError(_("Your opponent is not out of time"), 0);
12514             }
12515             break;
12516         }
12517     }
12518 }
12519
12520 void
12521 DrawEvent()
12522 {
12523     /* Offer draw or accept pending draw offer from opponent */
12524
12525     if (appData.icsActive) {
12526         /* Note: tournament rules require draw offers to be
12527            made after you make your move but before you punch
12528            your clock.  Currently ICS doesn't let you do that;
12529            instead, you immediately punch your clock after making
12530            a move, but you can offer a draw at any time. */
12531
12532         SendToICS(ics_prefix);
12533         SendToICS("draw\n");
12534         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12535     } else if (cmailMsgLoaded) {
12536         if (currentMove == cmailOldMove &&
12537             commentList[cmailOldMove] != NULL &&
12538             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12539                    "Black offers a draw" : "White offers a draw")) {
12540             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12541             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12542         } else if (currentMove == cmailOldMove + 1) {
12543             char *offer = WhiteOnMove(cmailOldMove) ?
12544               "White offers a draw" : "Black offers a draw";
12545             AppendComment(currentMove, offer, TRUE);
12546             DisplayComment(currentMove - 1, offer);
12547             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12548         } else {
12549             DisplayError(_("You must make your move before offering a draw"), 0);
12550             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12551         }
12552     } else if (first.offeredDraw) {
12553         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12554     } else {
12555         if (first.sendDrawOffers) {
12556             SendToProgram("draw\n", &first);
12557             userOfferedDraw = TRUE;
12558         }
12559     }
12560 }
12561
12562 void
12563 AdjournEvent()
12564 {
12565     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12566
12567     if (appData.icsActive) {
12568         SendToICS(ics_prefix);
12569         SendToICS("adjourn\n");
12570     } else {
12571         /* Currently GNU Chess doesn't offer or accept Adjourns */
12572     }
12573 }
12574
12575
12576 void
12577 AbortEvent()
12578 {
12579     /* Offer Abort or accept pending Abort offer from opponent */
12580
12581     if (appData.icsActive) {
12582         SendToICS(ics_prefix);
12583         SendToICS("abort\n");
12584     } else {
12585         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12586     }
12587 }
12588
12589 void
12590 ResignEvent()
12591 {
12592     /* Resign.  You can do this even if it's not your turn. */
12593
12594     if (appData.icsActive) {
12595         SendToICS(ics_prefix);
12596         SendToICS("resign\n");
12597     } else {
12598         switch (gameMode) {
12599           case MachinePlaysWhite:
12600             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12601             break;
12602           case MachinePlaysBlack:
12603             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12604             break;
12605           case EditGame:
12606             if (cmailMsgLoaded) {
12607                 TruncateGame();
12608                 if (WhiteOnMove(cmailOldMove)) {
12609                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12610                 } else {
12611                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12612                 }
12613                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12614             }
12615             break;
12616           default:
12617             break;
12618         }
12619     }
12620 }
12621
12622
12623 void
12624 StopObservingEvent()
12625 {
12626     /* Stop observing current games */
12627     SendToICS(ics_prefix);
12628     SendToICS("unobserve\n");
12629 }
12630
12631 void
12632 StopExaminingEvent()
12633 {
12634     /* Stop observing current game */
12635     SendToICS(ics_prefix);
12636     SendToICS("unexamine\n");
12637 }
12638
12639 void
12640 ForwardInner(target)
12641      int target;
12642 {
12643     int limit;
12644
12645     if (appData.debugMode)
12646         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12647                 target, currentMove, forwardMostMove);
12648
12649     if (gameMode == EditPosition)
12650       return;
12651
12652     if (gameMode == PlayFromGameFile && !pausing)
12653       PauseEvent();
12654
12655     if (gameMode == IcsExamining && pausing)
12656       limit = pauseExamForwardMostMove;
12657     else
12658       limit = forwardMostMove;
12659
12660     if (target > limit) target = limit;
12661
12662     if (target > 0 && moveList[target - 1][0]) {
12663         int fromX, fromY, toX, toY;
12664         toX = moveList[target - 1][2] - AAA;
12665         toY = moveList[target - 1][3] - ONE;
12666         if (moveList[target - 1][1] == '@') {
12667             if (appData.highlightLastMove) {
12668                 SetHighlights(-1, -1, toX, toY);
12669             }
12670         } else {
12671             fromX = moveList[target - 1][0] - AAA;
12672             fromY = moveList[target - 1][1] - ONE;
12673             if (target == currentMove + 1) {
12674                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12675             }
12676             if (appData.highlightLastMove) {
12677                 SetHighlights(fromX, fromY, toX, toY);
12678             }
12679         }
12680     }
12681     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12682         gameMode == Training || gameMode == PlayFromGameFile ||
12683         gameMode == AnalyzeFile) {
12684         while (currentMove < target) {
12685             SendMoveToProgram(currentMove++, &first);
12686         }
12687     } else {
12688         currentMove = target;
12689     }
12690
12691     if (gameMode == EditGame || gameMode == EndOfGame) {
12692         whiteTimeRemaining = timeRemaining[0][currentMove];
12693         blackTimeRemaining = timeRemaining[1][currentMove];
12694     }
12695     DisplayBothClocks();
12696     DisplayMove(currentMove - 1);
12697     DrawPosition(FALSE, boards[currentMove]);
12698     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12699     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12700         DisplayComment(currentMove - 1, commentList[currentMove]);
12701     }
12702 }
12703
12704
12705 void
12706 ForwardEvent()
12707 {
12708     if (gameMode == IcsExamining && !pausing) {
12709         SendToICS(ics_prefix);
12710         SendToICS("forward\n");
12711     } else {
12712         ForwardInner(currentMove + 1);
12713     }
12714 }
12715
12716 void
12717 ToEndEvent()
12718 {
12719     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12720         /* to optimze, we temporarily turn off analysis mode while we feed
12721          * the remaining moves to the engine. Otherwise we get analysis output
12722          * after each move.
12723          */
12724         if (first.analysisSupport) {
12725           SendToProgram("exit\nforce\n", &first);
12726           first.analyzing = FALSE;
12727         }
12728     }
12729
12730     if (gameMode == IcsExamining && !pausing) {
12731         SendToICS(ics_prefix);
12732         SendToICS("forward 999999\n");
12733     } else {
12734         ForwardInner(forwardMostMove);
12735     }
12736
12737     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12738         /* we have fed all the moves, so reactivate analysis mode */
12739         SendToProgram("analyze\n", &first);
12740         first.analyzing = TRUE;
12741         /*first.maybeThinking = TRUE;*/
12742         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12743     }
12744 }
12745
12746 void
12747 BackwardInner(target)
12748      int target;
12749 {
12750     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12751
12752     if (appData.debugMode)
12753         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12754                 target, currentMove, forwardMostMove);
12755
12756     if (gameMode == EditPosition) return;
12757     if (currentMove <= backwardMostMove) {
12758         ClearHighlights();
12759         DrawPosition(full_redraw, boards[currentMove]);
12760         return;
12761     }
12762     if (gameMode == PlayFromGameFile && !pausing)
12763       PauseEvent();
12764
12765     if (moveList[target][0]) {
12766         int fromX, fromY, toX, toY;
12767         toX = moveList[target][2] - AAA;
12768         toY = moveList[target][3] - ONE;
12769         if (moveList[target][1] == '@') {
12770             if (appData.highlightLastMove) {
12771                 SetHighlights(-1, -1, toX, toY);
12772             }
12773         } else {
12774             fromX = moveList[target][0] - AAA;
12775             fromY = moveList[target][1] - ONE;
12776             if (target == currentMove - 1) {
12777                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12778             }
12779             if (appData.highlightLastMove) {
12780                 SetHighlights(fromX, fromY, toX, toY);
12781             }
12782         }
12783     }
12784     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12785         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12786         while (currentMove > target) {
12787             SendToProgram("undo\n", &first);
12788             currentMove--;
12789         }
12790     } else {
12791         currentMove = target;
12792     }
12793
12794     if (gameMode == EditGame || gameMode == EndOfGame) {
12795         whiteTimeRemaining = timeRemaining[0][currentMove];
12796         blackTimeRemaining = timeRemaining[1][currentMove];
12797     }
12798     DisplayBothClocks();
12799     DisplayMove(currentMove - 1);
12800     DrawPosition(full_redraw, boards[currentMove]);
12801     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12802     // [HGM] PV info: routine tests if comment empty
12803     DisplayComment(currentMove - 1, commentList[currentMove]);
12804 }
12805
12806 void
12807 BackwardEvent()
12808 {
12809     if (gameMode == IcsExamining && !pausing) {
12810         SendToICS(ics_prefix);
12811         SendToICS("backward\n");
12812     } else {
12813         BackwardInner(currentMove - 1);
12814     }
12815 }
12816
12817 void
12818 ToStartEvent()
12819 {
12820     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12821         /* to optimize, we temporarily turn off analysis mode while we undo
12822          * all the moves. Otherwise we get analysis output after each undo.
12823          */
12824         if (first.analysisSupport) {
12825           SendToProgram("exit\nforce\n", &first);
12826           first.analyzing = FALSE;
12827         }
12828     }
12829
12830     if (gameMode == IcsExamining && !pausing) {
12831         SendToICS(ics_prefix);
12832         SendToICS("backward 999999\n");
12833     } else {
12834         BackwardInner(backwardMostMove);
12835     }
12836
12837     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12838         /* we have fed all the moves, so reactivate analysis mode */
12839         SendToProgram("analyze\n", &first);
12840         first.analyzing = TRUE;
12841         /*first.maybeThinking = TRUE;*/
12842         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12843     }
12844 }
12845
12846 void
12847 ToNrEvent(int to)
12848 {
12849   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12850   if (to >= forwardMostMove) to = forwardMostMove;
12851   if (to <= backwardMostMove) to = backwardMostMove;
12852   if (to < currentMove) {
12853     BackwardInner(to);
12854   } else {
12855     ForwardInner(to);
12856   }
12857 }
12858
12859 void
12860 RevertEvent(Boolean annotate)
12861 {
12862     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12863         return;
12864     }
12865     if (gameMode != IcsExamining) {
12866         DisplayError(_("You are not examining a game"), 0);
12867         return;
12868     }
12869     if (pausing) {
12870         DisplayError(_("You can't revert while pausing"), 0);
12871         return;
12872     }
12873     SendToICS(ics_prefix);
12874     SendToICS("revert\n");
12875 }
12876
12877 void
12878 RetractMoveEvent()
12879 {
12880     switch (gameMode) {
12881       case MachinePlaysWhite:
12882       case MachinePlaysBlack:
12883         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12884             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12885             return;
12886         }
12887         if (forwardMostMove < 2) return;
12888         currentMove = forwardMostMove = forwardMostMove - 2;
12889         whiteTimeRemaining = timeRemaining[0][currentMove];
12890         blackTimeRemaining = timeRemaining[1][currentMove];
12891         DisplayBothClocks();
12892         DisplayMove(currentMove - 1);
12893         ClearHighlights();/*!! could figure this out*/
12894         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12895         SendToProgram("remove\n", &first);
12896         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12897         break;
12898
12899       case BeginningOfGame:
12900       default:
12901         break;
12902
12903       case IcsPlayingWhite:
12904       case IcsPlayingBlack:
12905         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12906             SendToICS(ics_prefix);
12907             SendToICS("takeback 2\n");
12908         } else {
12909             SendToICS(ics_prefix);
12910             SendToICS("takeback 1\n");
12911         }
12912         break;
12913     }
12914 }
12915
12916 void
12917 MoveNowEvent()
12918 {
12919     ChessProgramState *cps;
12920
12921     switch (gameMode) {
12922       case MachinePlaysWhite:
12923         if (!WhiteOnMove(forwardMostMove)) {
12924             DisplayError(_("It is your turn"), 0);
12925             return;
12926         }
12927         cps = &first;
12928         break;
12929       case MachinePlaysBlack:
12930         if (WhiteOnMove(forwardMostMove)) {
12931             DisplayError(_("It is your turn"), 0);
12932             return;
12933         }
12934         cps = &first;
12935         break;
12936       case TwoMachinesPlay:
12937         if (WhiteOnMove(forwardMostMove) ==
12938             (first.twoMachinesColor[0] == 'w')) {
12939             cps = &first;
12940         } else {
12941             cps = &second;
12942         }
12943         break;
12944       case BeginningOfGame:
12945       default:
12946         return;
12947     }
12948     SendToProgram("?\n", cps);
12949 }
12950
12951 void
12952 TruncateGameEvent()
12953 {
12954     EditGameEvent();
12955     if (gameMode != EditGame) return;
12956     TruncateGame();
12957 }
12958
12959 void
12960 TruncateGame()
12961 {
12962     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12963     if (forwardMostMove > currentMove) {
12964         if (gameInfo.resultDetails != NULL) {
12965             free(gameInfo.resultDetails);
12966             gameInfo.resultDetails = NULL;
12967             gameInfo.result = GameUnfinished;
12968         }
12969         forwardMostMove = currentMove;
12970         HistorySet(parseList, backwardMostMove, forwardMostMove,
12971                    currentMove-1);
12972     }
12973 }
12974
12975 void
12976 HintEvent()
12977 {
12978     if (appData.noChessProgram) return;
12979     switch (gameMode) {
12980       case MachinePlaysWhite:
12981         if (WhiteOnMove(forwardMostMove)) {
12982             DisplayError(_("Wait until your turn"), 0);
12983             return;
12984         }
12985         break;
12986       case BeginningOfGame:
12987       case MachinePlaysBlack:
12988         if (!WhiteOnMove(forwardMostMove)) {
12989             DisplayError(_("Wait until your turn"), 0);
12990             return;
12991         }
12992         break;
12993       default:
12994         DisplayError(_("No hint available"), 0);
12995         return;
12996     }
12997     SendToProgram("hint\n", &first);
12998     hintRequested = TRUE;
12999 }
13000
13001 void
13002 BookEvent()
13003 {
13004     if (appData.noChessProgram) return;
13005     switch (gameMode) {
13006       case MachinePlaysWhite:
13007         if (WhiteOnMove(forwardMostMove)) {
13008             DisplayError(_("Wait until your turn"), 0);
13009             return;
13010         }
13011         break;
13012       case BeginningOfGame:
13013       case MachinePlaysBlack:
13014         if (!WhiteOnMove(forwardMostMove)) {
13015             DisplayError(_("Wait until your turn"), 0);
13016             return;
13017         }
13018         break;
13019       case EditPosition:
13020         EditPositionDone(TRUE);
13021         break;
13022       case TwoMachinesPlay:
13023         return;
13024       default:
13025         break;
13026     }
13027     SendToProgram("bk\n", &first);
13028     bookOutput[0] = NULLCHAR;
13029     bookRequested = TRUE;
13030 }
13031
13032 void
13033 AboutGameEvent()
13034 {
13035     char *tags = PGNTags(&gameInfo);
13036     TagsPopUp(tags, CmailMsg());
13037     free(tags);
13038 }
13039
13040 /* end button procedures */
13041
13042 void
13043 PrintPosition(fp, move)
13044      FILE *fp;
13045      int move;
13046 {
13047     int i, j;
13048
13049     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13050         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13051             char c = PieceToChar(boards[move][i][j]);
13052             fputc(c == 'x' ? '.' : c, fp);
13053             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13054         }
13055     }
13056     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13057       fprintf(fp, "white to play\n");
13058     else
13059       fprintf(fp, "black to play\n");
13060 }
13061
13062 void
13063 PrintOpponents(fp)
13064      FILE *fp;
13065 {
13066     if (gameInfo.white != NULL) {
13067         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13068     } else {
13069         fprintf(fp, "\n");
13070     }
13071 }
13072
13073 /* Find last component of program's own name, using some heuristics */
13074 void
13075 TidyProgramName(prog, host, buf)
13076      char *prog, *host, buf[MSG_SIZ];
13077 {
13078     char *p, *q;
13079     int local = (strcmp(host, "localhost") == 0);
13080     while (!local && (p = strchr(prog, ';')) != NULL) {
13081         p++;
13082         while (*p == ' ') p++;
13083         prog = p;
13084     }
13085     if (*prog == '"' || *prog == '\'') {
13086         q = strchr(prog + 1, *prog);
13087     } else {
13088         q = strchr(prog, ' ');
13089     }
13090     if (q == NULL) q = prog + strlen(prog);
13091     p = q;
13092     while (p >= prog && *p != '/' && *p != '\\') p--;
13093     p++;
13094     if(p == prog && *p == '"') p++;
13095     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13096     memcpy(buf, p, q - p);
13097     buf[q - p] = NULLCHAR;
13098     if (!local) {
13099         strcat(buf, "@");
13100         strcat(buf, host);
13101     }
13102 }
13103
13104 char *
13105 TimeControlTagValue()
13106 {
13107     char buf[MSG_SIZ];
13108     if (!appData.clockMode) {
13109       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13110     } else if (movesPerSession > 0) {
13111       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13112     } else if (timeIncrement == 0) {
13113       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13114     } else {
13115       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13116     }
13117     return StrSave(buf);
13118 }
13119
13120 void
13121 SetGameInfo()
13122 {
13123     /* This routine is used only for certain modes */
13124     VariantClass v = gameInfo.variant;
13125     ChessMove r = GameUnfinished;
13126     char *p = NULL;
13127
13128     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13129         r = gameInfo.result;
13130         p = gameInfo.resultDetails;
13131         gameInfo.resultDetails = NULL;
13132     }
13133     ClearGameInfo(&gameInfo);
13134     gameInfo.variant = v;
13135
13136     switch (gameMode) {
13137       case MachinePlaysWhite:
13138         gameInfo.event = StrSave( appData.pgnEventHeader );
13139         gameInfo.site = StrSave(HostName());
13140         gameInfo.date = PGNDate();
13141         gameInfo.round = StrSave("-");
13142         gameInfo.white = StrSave(first.tidy);
13143         gameInfo.black = StrSave(UserName());
13144         gameInfo.timeControl = TimeControlTagValue();
13145         break;
13146
13147       case MachinePlaysBlack:
13148         gameInfo.event = StrSave( appData.pgnEventHeader );
13149         gameInfo.site = StrSave(HostName());
13150         gameInfo.date = PGNDate();
13151         gameInfo.round = StrSave("-");
13152         gameInfo.white = StrSave(UserName());
13153         gameInfo.black = StrSave(first.tidy);
13154         gameInfo.timeControl = TimeControlTagValue();
13155         break;
13156
13157       case TwoMachinesPlay:
13158         gameInfo.event = StrSave( appData.pgnEventHeader );
13159         gameInfo.site = StrSave(HostName());
13160         gameInfo.date = PGNDate();
13161         if (matchGame > 0) {
13162             char buf[MSG_SIZ];
13163             snprintf(buf, MSG_SIZ, "%d", matchGame);
13164             gameInfo.round = StrSave(buf);
13165         } else {
13166             gameInfo.round = StrSave("-");
13167         }
13168         if (first.twoMachinesColor[0] == 'w') {
13169             gameInfo.white = StrSave(first.tidy);
13170             gameInfo.black = StrSave(second.tidy);
13171         } else {
13172             gameInfo.white = StrSave(second.tidy);
13173             gameInfo.black = StrSave(first.tidy);
13174         }
13175         gameInfo.timeControl = TimeControlTagValue();
13176         break;
13177
13178       case EditGame:
13179         gameInfo.event = StrSave("Edited game");
13180         gameInfo.site = StrSave(HostName());
13181         gameInfo.date = PGNDate();
13182         gameInfo.round = StrSave("-");
13183         gameInfo.white = StrSave("-");
13184         gameInfo.black = StrSave("-");
13185         gameInfo.result = r;
13186         gameInfo.resultDetails = p;
13187         break;
13188
13189       case EditPosition:
13190         gameInfo.event = StrSave("Edited position");
13191         gameInfo.site = StrSave(HostName());
13192         gameInfo.date = PGNDate();
13193         gameInfo.round = StrSave("-");
13194         gameInfo.white = StrSave("-");
13195         gameInfo.black = StrSave("-");
13196         break;
13197
13198       case IcsPlayingWhite:
13199       case IcsPlayingBlack:
13200       case IcsObserving:
13201       case IcsExamining:
13202         break;
13203
13204       case PlayFromGameFile:
13205         gameInfo.event = StrSave("Game from non-PGN file");
13206         gameInfo.site = StrSave(HostName());
13207         gameInfo.date = PGNDate();
13208         gameInfo.round = StrSave("-");
13209         gameInfo.white = StrSave("?");
13210         gameInfo.black = StrSave("?");
13211         break;
13212
13213       default:
13214         break;
13215     }
13216 }
13217
13218 void
13219 ReplaceComment(index, text)
13220      int index;
13221      char *text;
13222 {
13223     int len;
13224
13225     while (*text == '\n') text++;
13226     len = strlen(text);
13227     while (len > 0 && text[len - 1] == '\n') len--;
13228
13229     if (commentList[index] != NULL)
13230       free(commentList[index]);
13231
13232     if (len == 0) {
13233         commentList[index] = NULL;
13234         return;
13235     }
13236   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13237       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13238       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13239     commentList[index] = (char *) malloc(len + 2);
13240     strncpy(commentList[index], text, len);
13241     commentList[index][len] = '\n';
13242     commentList[index][len + 1] = NULLCHAR;
13243   } else {
13244     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13245     char *p;
13246     commentList[index] = (char *) malloc(len + 7);
13247     safeStrCpy(commentList[index], "{\n", 3);
13248     safeStrCpy(commentList[index]+2, text, len+1);
13249     commentList[index][len+2] = NULLCHAR;
13250     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13251     strcat(commentList[index], "\n}\n");
13252   }
13253 }
13254
13255 void
13256 CrushCRs(text)
13257      char *text;
13258 {
13259   char *p = text;
13260   char *q = text;
13261   char ch;
13262
13263   do {
13264     ch = *p++;
13265     if (ch == '\r') continue;
13266     *q++ = ch;
13267   } while (ch != '\0');
13268 }
13269
13270 void
13271 AppendComment(index, text, addBraces)
13272      int index;
13273      char *text;
13274      Boolean addBraces; // [HGM] braces: tells if we should add {}
13275 {
13276     int oldlen, len;
13277     char *old;
13278
13279 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13280     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13281
13282     CrushCRs(text);
13283     while (*text == '\n') text++;
13284     len = strlen(text);
13285     while (len > 0 && text[len - 1] == '\n') len--;
13286
13287     if (len == 0) return;
13288
13289     if (commentList[index] != NULL) {
13290         old = commentList[index];
13291         oldlen = strlen(old);
13292         while(commentList[index][oldlen-1] ==  '\n')
13293           commentList[index][--oldlen] = NULLCHAR;
13294         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13295         safeStrCpy(commentList[index], old, oldlen);
13296         free(old);
13297         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13298         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13299           if(addBraces) addBraces = FALSE; else { text++; len--; }
13300           while (*text == '\n') { text++; len--; }
13301           commentList[index][--oldlen] = NULLCHAR;
13302       }
13303         if(addBraces) strcat(commentList[index], "\n{\n");
13304         else          strcat(commentList[index], "\n");
13305         strcat(commentList[index], text);
13306         if(addBraces) strcat(commentList[index], "\n}\n");
13307         else          strcat(commentList[index], "\n");
13308     } else {
13309         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13310         if(addBraces)
13311           safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
13312         else commentList[index][0] = NULLCHAR;
13313         strcat(commentList[index], text);
13314         strcat(commentList[index], "\n");
13315         if(addBraces) strcat(commentList[index], "}\n");
13316     }
13317 }
13318
13319 static char * FindStr( char * text, char * sub_text )
13320 {
13321     char * result = strstr( text, sub_text );
13322
13323     if( result != NULL ) {
13324         result += strlen( sub_text );
13325     }
13326
13327     return result;
13328 }
13329
13330 /* [AS] Try to extract PV info from PGN comment */
13331 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13332 char *GetInfoFromComment( int index, char * text )
13333 {
13334     char * sep = text;
13335
13336     if( text != NULL && index > 0 ) {
13337         int score = 0;
13338         int depth = 0;
13339         int time = -1, sec = 0, deci;
13340         char * s_eval = FindStr( text, "[%eval " );
13341         char * s_emt = FindStr( text, "[%emt " );
13342
13343         if( s_eval != NULL || s_emt != NULL ) {
13344             /* New style */
13345             char delim;
13346
13347             if( s_eval != NULL ) {
13348                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13349                     return text;
13350                 }
13351
13352                 if( delim != ']' ) {
13353                     return text;
13354                 }
13355             }
13356
13357             if( s_emt != NULL ) {
13358             }
13359                 return text;
13360         }
13361         else {
13362             /* We expect something like: [+|-]nnn.nn/dd */
13363             int score_lo = 0;
13364
13365             if(*text != '{') return text; // [HGM] braces: must be normal comment
13366
13367             sep = strchr( text, '/' );
13368             if( sep == NULL || sep < (text+4) ) {
13369                 return text;
13370             }
13371
13372             time = -1; sec = -1; deci = -1;
13373             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13374                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13375                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13376                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13377                 return text;
13378             }
13379
13380             if( score_lo < 0 || score_lo >= 100 ) {
13381                 return text;
13382             }
13383
13384             if(sec >= 0) time = 600*time + 10*sec; else
13385             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13386
13387             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13388
13389             /* [HGM] PV time: now locate end of PV info */
13390             while( *++sep >= '0' && *sep <= '9'); // strip depth
13391             if(time >= 0)
13392             while( *++sep >= '0' && *sep <= '9'); // strip time
13393             if(sec >= 0)
13394             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13395             if(deci >= 0)
13396             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13397             while(*sep == ' ') sep++;
13398         }
13399
13400         if( depth <= 0 ) {
13401             return text;
13402         }
13403
13404         if( time < 0 ) {
13405             time = -1;
13406         }
13407
13408         pvInfoList[index-1].depth = depth;
13409         pvInfoList[index-1].score = score;
13410         pvInfoList[index-1].time  = 10*time; // centi-sec
13411         if(*sep == '}') *sep = 0; else *--sep = '{';
13412     }
13413     return sep;
13414 }
13415
13416 void
13417 SendToProgram(message, cps)
13418      char *message;
13419      ChessProgramState *cps;
13420 {
13421     int count, outCount, error;
13422     char buf[MSG_SIZ];
13423
13424     if (cps->pr == NULL) return;
13425     Attention(cps);
13426
13427     if (appData.debugMode) {
13428         TimeMark now;
13429         GetTimeMark(&now);
13430         fprintf(debugFP, "%ld >%-6s: %s",
13431                 SubtractTimeMarks(&now, &programStartTime),
13432                 cps->which, message);
13433     }
13434
13435     count = strlen(message);
13436     outCount = OutputToProcess(cps->pr, message, count, &error);
13437     if (outCount < count && !exiting
13438                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13439       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13440         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13441             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13442                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13443                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13444             } else {
13445                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13446             }
13447             gameInfo.resultDetails = StrSave(buf);
13448         }
13449         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13450     }
13451 }
13452
13453 void
13454 ReceiveFromProgram(isr, closure, message, count, error)
13455      InputSourceRef isr;
13456      VOIDSTAR closure;
13457      char *message;
13458      int count;
13459      int error;
13460 {
13461     char *end_str;
13462     char buf[MSG_SIZ];
13463     ChessProgramState *cps = (ChessProgramState *)closure;
13464
13465     if (isr != cps->isr) return; /* Killed intentionally */
13466     if (count <= 0) {
13467         if (count == 0) {
13468             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13469                     cps->which, cps->program);
13470         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13471                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13472                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13473                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13474                 } else {
13475                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13476                 }
13477                 gameInfo.resultDetails = StrSave(buf);
13478             }
13479             RemoveInputSource(cps->isr);
13480             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13481         } else {
13482             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13483                     cps->which, cps->program);
13484             RemoveInputSource(cps->isr);
13485
13486             /* [AS] Program is misbehaving badly... kill it */
13487             if( count == -2 ) {
13488                 DestroyChildProcess( cps->pr, 9 );
13489                 cps->pr = NoProc;
13490             }
13491
13492             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13493         }
13494         return;
13495     }
13496
13497     if ((end_str = strchr(message, '\r')) != NULL)
13498       *end_str = NULLCHAR;
13499     if ((end_str = strchr(message, '\n')) != NULL)
13500       *end_str = NULLCHAR;
13501
13502     if (appData.debugMode) {
13503         TimeMark now; int print = 1;
13504         char *quote = ""; char c; int i;
13505
13506         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13507                 char start = message[0];
13508                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13509                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13510                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13511                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13512                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13513                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13514                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13515                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13516                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13517                     print = (appData.engineComments >= 2);
13518                 }
13519                 message[0] = start; // restore original message
13520         }
13521         if(print) {
13522                 GetTimeMark(&now);
13523                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13524                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13525                         quote,
13526                         message);
13527         }
13528     }
13529
13530     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13531     if (appData.icsEngineAnalyze) {
13532         if (strstr(message, "whisper") != NULL ||
13533              strstr(message, "kibitz") != NULL ||
13534             strstr(message, "tellics") != NULL) return;
13535     }
13536
13537     HandleMachineMove(message, cps);
13538 }
13539
13540
13541 void
13542 SendTimeControl(cps, mps, tc, inc, sd, st)
13543      ChessProgramState *cps;
13544      int mps, inc, sd, st;
13545      long tc;
13546 {
13547     char buf[MSG_SIZ];
13548     int seconds;
13549
13550     if( timeControl_2 > 0 ) {
13551         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13552             tc = timeControl_2;
13553         }
13554     }
13555     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13556     inc /= cps->timeOdds;
13557     st  /= cps->timeOdds;
13558
13559     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13560
13561     if (st > 0) {
13562       /* Set exact time per move, normally using st command */
13563       if (cps->stKludge) {
13564         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13565         seconds = st % 60;
13566         if (seconds == 0) {
13567           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13568         } else {
13569           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13570         }
13571       } else {
13572         snprintf(buf, MSG_SIZ, "st %d\n", st);
13573       }
13574     } else {
13575       /* Set conventional or incremental time control, using level command */
13576       if (seconds == 0) {
13577         /* Note old gnuchess bug -- minutes:seconds used to not work.
13578            Fixed in later versions, but still avoid :seconds
13579            when seconds is 0. */
13580         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13581       } else {
13582         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13583                  seconds, inc/1000.);
13584       }
13585     }
13586     SendToProgram(buf, cps);
13587
13588     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13589     /* Orthogonally, limit search to given depth */
13590     if (sd > 0) {
13591       if (cps->sdKludge) {
13592         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13593       } else {
13594         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13595       }
13596       SendToProgram(buf, cps);
13597     }
13598
13599     if(cps->nps > 0) { /* [HGM] nps */
13600         if(cps->supportsNPS == FALSE)
13601           cps->nps = -1; // don't use if engine explicitly says not supported!
13602         else {
13603           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13604           SendToProgram(buf, cps);
13605         }
13606     }
13607 }
13608
13609 ChessProgramState *WhitePlayer()
13610 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13611 {
13612     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13613        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13614         return &second;
13615     return &first;
13616 }
13617
13618 void
13619 SendTimeRemaining(cps, machineWhite)
13620      ChessProgramState *cps;
13621      int /*boolean*/ machineWhite;
13622 {
13623     char message[MSG_SIZ];
13624     long time, otime;
13625
13626     /* Note: this routine must be called when the clocks are stopped
13627        or when they have *just* been set or switched; otherwise
13628        it will be off by the time since the current tick started.
13629     */
13630     if (machineWhite) {
13631         time = whiteTimeRemaining / 10;
13632         otime = blackTimeRemaining / 10;
13633     } else {
13634         time = blackTimeRemaining / 10;
13635         otime = whiteTimeRemaining / 10;
13636     }
13637     /* [HGM] translate opponent's time by time-odds factor */
13638     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13639     if (appData.debugMode) {
13640         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13641     }
13642
13643     if (time <= 0) time = 1;
13644     if (otime <= 0) otime = 1;
13645
13646     snprintf(message, MSG_SIZ, "time %ld\n", time);
13647     SendToProgram(message, cps);
13648
13649     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13650     SendToProgram(message, cps);
13651 }
13652
13653 int
13654 BoolFeature(p, name, loc, cps)
13655      char **p;
13656      char *name;
13657      int *loc;
13658      ChessProgramState *cps;
13659 {
13660   char buf[MSG_SIZ];
13661   int len = strlen(name);
13662   int val;
13663
13664   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13665     (*p) += len + 1;
13666     sscanf(*p, "%d", &val);
13667     *loc = (val != 0);
13668     while (**p && **p != ' ')
13669       (*p)++;
13670     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13671     SendToProgram(buf, cps);
13672     return TRUE;
13673   }
13674   return FALSE;
13675 }
13676
13677 int
13678 IntFeature(p, name, loc, cps)
13679      char **p;
13680      char *name;
13681      int *loc;
13682      ChessProgramState *cps;
13683 {
13684   char buf[MSG_SIZ];
13685   int len = strlen(name);
13686   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13687     (*p) += len + 1;
13688     sscanf(*p, "%d", loc);
13689     while (**p && **p != ' ') (*p)++;
13690     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13691     SendToProgram(buf, cps);
13692     return TRUE;
13693   }
13694   return FALSE;
13695 }
13696
13697 int
13698 StringFeature(p, name, loc, cps)
13699      char **p;
13700      char *name;
13701      char loc[];
13702      ChessProgramState *cps;
13703 {
13704   char buf[MSG_SIZ];
13705   int len = strlen(name);
13706   if (strncmp((*p), name, len) == 0
13707       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13708     (*p) += len + 2;
13709     sscanf(*p, "%[^\"]", loc);
13710     while (**p && **p != '\"') (*p)++;
13711     if (**p == '\"') (*p)++;
13712     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13713     SendToProgram(buf, cps);
13714     return TRUE;
13715   }
13716   return FALSE;
13717 }
13718
13719 int
13720 ParseOption(Option *opt, ChessProgramState *cps)
13721 // [HGM] options: process the string that defines an engine option, and determine
13722 // name, type, default value, and allowed value range
13723 {
13724         char *p, *q, buf[MSG_SIZ];
13725         int n, min = (-1)<<31, max = 1<<31, def;
13726
13727         if(p = strstr(opt->name, " -spin ")) {
13728             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13729             if(max < min) max = min; // enforce consistency
13730             if(def < min) def = min;
13731             if(def > max) def = max;
13732             opt->value = def;
13733             opt->min = min;
13734             opt->max = max;
13735             opt->type = Spin;
13736         } else if((p = strstr(opt->name, " -slider "))) {
13737             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13738             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13739             if(max < min) max = min; // enforce consistency
13740             if(def < min) def = min;
13741             if(def > max) def = max;
13742             opt->value = def;
13743             opt->min = min;
13744             opt->max = max;
13745             opt->type = Spin; // Slider;
13746         } else if((p = strstr(opt->name, " -string "))) {
13747             opt->textValue = p+9;
13748             opt->type = TextBox;
13749         } else if((p = strstr(opt->name, " -file "))) {
13750             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13751             opt->textValue = p+7;
13752             opt->type = TextBox; // FileName;
13753         } else if((p = strstr(opt->name, " -path "))) {
13754             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13755             opt->textValue = p+7;
13756             opt->type = TextBox; // PathName;
13757         } else if(p = strstr(opt->name, " -check ")) {
13758             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13759             opt->value = (def != 0);
13760             opt->type = CheckBox;
13761         } else if(p = strstr(opt->name, " -combo ")) {
13762             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13763             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13764             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13765             opt->value = n = 0;
13766             while(q = StrStr(q, " /// ")) {
13767                 n++; *q = 0;    // count choices, and null-terminate each of them
13768                 q += 5;
13769                 if(*q == '*') { // remember default, which is marked with * prefix
13770                     q++;
13771                     opt->value = n;
13772                 }
13773                 cps->comboList[cps->comboCnt++] = q;
13774             }
13775             cps->comboList[cps->comboCnt++] = NULL;
13776             opt->max = n + 1;
13777             opt->type = ComboBox;
13778         } else if(p = strstr(opt->name, " -button")) {
13779             opt->type = Button;
13780         } else if(p = strstr(opt->name, " -save")) {
13781             opt->type = SaveButton;
13782         } else return FALSE;
13783         *p = 0; // terminate option name
13784         // now look if the command-line options define a setting for this engine option.
13785         if(cps->optionSettings && cps->optionSettings[0])
13786             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13787         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13788           snprintf(buf, MSG_SIZ, "option %s", p);
13789                 if(p = strstr(buf, ",")) *p = 0;
13790                 strcat(buf, "\n");
13791                 SendToProgram(buf, cps);
13792         }
13793         return TRUE;
13794 }
13795
13796 void
13797 FeatureDone(cps, val)
13798      ChessProgramState* cps;
13799      int val;
13800 {
13801   DelayedEventCallback cb = GetDelayedEvent();
13802   if ((cb == InitBackEnd3 && cps == &first) ||
13803       (cb == TwoMachinesEventIfReady && cps == &second)) {
13804     CancelDelayedEvent();
13805     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13806   }
13807   cps->initDone = val;
13808 }
13809
13810 /* Parse feature command from engine */
13811 void
13812 ParseFeatures(args, cps)
13813      char* args;
13814      ChessProgramState *cps;
13815 {
13816   char *p = args;
13817   char *q;
13818   int val;
13819   char buf[MSG_SIZ];
13820
13821   for (;;) {
13822     while (*p == ' ') p++;
13823     if (*p == NULLCHAR) return;
13824
13825     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13826     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13827     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13828     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13829     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13830     if (BoolFeature(&p, "reuse", &val, cps)) {
13831       /* Engine can disable reuse, but can't enable it if user said no */
13832       if (!val) cps->reuse = FALSE;
13833       continue;
13834     }
13835     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13836     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13837       if (gameMode == TwoMachinesPlay) {
13838         DisplayTwoMachinesTitle();
13839       } else {
13840         DisplayTitle("");
13841       }
13842       continue;
13843     }
13844     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13845     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13846     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13847     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13848     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13849     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13850     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13851     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13852     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13853     if (IntFeature(&p, "done", &val, cps)) {
13854       FeatureDone(cps, val);
13855       continue;
13856     }
13857     /* Added by Tord: */
13858     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13859     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13860     /* End of additions by Tord */
13861
13862     /* [HGM] added features: */
13863     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13864     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13865     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13866     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13867     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13868     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13869     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13870         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13871           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13872             SendToProgram(buf, cps);
13873             continue;
13874         }
13875         if(cps->nrOptions >= MAX_OPTIONS) {
13876             cps->nrOptions--;
13877             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13878             DisplayError(buf, 0);
13879         }
13880         continue;
13881     }
13882     /* End of additions by HGM */
13883
13884     /* unknown feature: complain and skip */
13885     q = p;
13886     while (*q && *q != '=') q++;
13887     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13888     SendToProgram(buf, cps);
13889     p = q;
13890     if (*p == '=') {
13891       p++;
13892       if (*p == '\"') {
13893         p++;
13894         while (*p && *p != '\"') p++;
13895         if (*p == '\"') p++;
13896       } else {
13897         while (*p && *p != ' ') p++;
13898       }
13899     }
13900   }
13901
13902 }
13903
13904 void
13905 PeriodicUpdatesEvent(newState)
13906      int newState;
13907 {
13908     if (newState == appData.periodicUpdates)
13909       return;
13910
13911     appData.periodicUpdates=newState;
13912
13913     /* Display type changes, so update it now */
13914 //    DisplayAnalysis();
13915
13916     /* Get the ball rolling again... */
13917     if (newState) {
13918         AnalysisPeriodicEvent(1);
13919         StartAnalysisClock();
13920     }
13921 }
13922
13923 void
13924 PonderNextMoveEvent(newState)
13925      int newState;
13926 {
13927     if (newState == appData.ponderNextMove) return;
13928     if (gameMode == EditPosition) EditPositionDone(TRUE);
13929     if (newState) {
13930         SendToProgram("hard\n", &first);
13931         if (gameMode == TwoMachinesPlay) {
13932             SendToProgram("hard\n", &second);
13933         }
13934     } else {
13935         SendToProgram("easy\n", &first);
13936         thinkOutput[0] = NULLCHAR;
13937         if (gameMode == TwoMachinesPlay) {
13938             SendToProgram("easy\n", &second);
13939         }
13940     }
13941     appData.ponderNextMove = newState;
13942 }
13943
13944 void
13945 NewSettingEvent(option, feature, command, value)
13946      char *command;
13947      int option, value, *feature;
13948 {
13949     char buf[MSG_SIZ];
13950
13951     if (gameMode == EditPosition) EditPositionDone(TRUE);
13952     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
13953     if(feature == NULL || *feature) SendToProgram(buf, &first);
13954     if (gameMode == TwoMachinesPlay) {
13955         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13956     }
13957 }
13958
13959 void
13960 ShowThinkingEvent()
13961 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13962 {
13963     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13964     int newState = appData.showThinking
13965         // [HGM] thinking: other features now need thinking output as well
13966         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13967
13968     if (oldState == newState) return;
13969     oldState = newState;
13970     if (gameMode == EditPosition) EditPositionDone(TRUE);
13971     if (oldState) {
13972         SendToProgram("post\n", &first);
13973         if (gameMode == TwoMachinesPlay) {
13974             SendToProgram("post\n", &second);
13975         }
13976     } else {
13977         SendToProgram("nopost\n", &first);
13978         thinkOutput[0] = NULLCHAR;
13979         if (gameMode == TwoMachinesPlay) {
13980             SendToProgram("nopost\n", &second);
13981         }
13982     }
13983 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13984 }
13985
13986 void
13987 AskQuestionEvent(title, question, replyPrefix, which)
13988      char *title; char *question; char *replyPrefix; char *which;
13989 {
13990   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13991   if (pr == NoProc) return;
13992   AskQuestion(title, question, replyPrefix, pr);
13993 }
13994
13995 void
13996 DisplayMove(moveNumber)
13997      int moveNumber;
13998 {
13999     char message[MSG_SIZ];
14000     char res[MSG_SIZ];
14001     char cpThinkOutput[MSG_SIZ];
14002
14003     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14004
14005     if (moveNumber == forwardMostMove - 1 ||
14006         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14007
14008         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14009
14010         if (strchr(cpThinkOutput, '\n')) {
14011             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14012         }
14013     } else {
14014         *cpThinkOutput = NULLCHAR;
14015     }
14016
14017     /* [AS] Hide thinking from human user */
14018     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14019         *cpThinkOutput = NULLCHAR;
14020         if( thinkOutput[0] != NULLCHAR ) {
14021             int i;
14022
14023             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14024                 cpThinkOutput[i] = '.';
14025             }
14026             cpThinkOutput[i] = NULLCHAR;
14027             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14028         }
14029     }
14030
14031     if (moveNumber == forwardMostMove - 1 &&
14032         gameInfo.resultDetails != NULL) {
14033         if (gameInfo.resultDetails[0] == NULLCHAR) {
14034           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14035         } else {
14036           snprintf(res, MSG_SIZ, " {%s} %s",
14037                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14038         }
14039     } else {
14040         res[0] = NULLCHAR;
14041     }
14042
14043     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14044         DisplayMessage(res, cpThinkOutput);
14045     } else {
14046       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14047                 WhiteOnMove(moveNumber) ? " " : ".. ",
14048                 parseList[moveNumber], res);
14049         DisplayMessage(message, cpThinkOutput);
14050     }
14051 }
14052
14053 void
14054 DisplayComment(moveNumber, text)
14055      int moveNumber;
14056      char *text;
14057 {
14058     char title[MSG_SIZ];
14059     char buf[8000]; // comment can be long!
14060     int score, depth;
14061
14062     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14063       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14064     } else {
14065       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14066               WhiteOnMove(moveNumber) ? " " : ".. ",
14067               parseList[moveNumber]);
14068     }
14069     // [HGM] PV info: display PV info together with (or as) comment
14070     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14071       if(text == NULL) text = "";
14072       score = pvInfoList[moveNumber].score;
14073       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14074               depth, (pvInfoList[moveNumber].time+50)/100, text);
14075       text = buf;
14076     }
14077     if (text != NULL && (appData.autoDisplayComment || commentUp))
14078         CommentPopUp(title, text);
14079 }
14080
14081 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14082  * might be busy thinking or pondering.  It can be omitted if your
14083  * gnuchess is configured to stop thinking immediately on any user
14084  * input.  However, that gnuchess feature depends on the FIONREAD
14085  * ioctl, which does not work properly on some flavors of Unix.
14086  */
14087 void
14088 Attention(cps)
14089      ChessProgramState *cps;
14090 {
14091 #if ATTENTION
14092     if (!cps->useSigint) return;
14093     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14094     switch (gameMode) {
14095       case MachinePlaysWhite:
14096       case MachinePlaysBlack:
14097       case TwoMachinesPlay:
14098       case IcsPlayingWhite:
14099       case IcsPlayingBlack:
14100       case AnalyzeMode:
14101       case AnalyzeFile:
14102         /* Skip if we know it isn't thinking */
14103         if (!cps->maybeThinking) return;
14104         if (appData.debugMode)
14105           fprintf(debugFP, "Interrupting %s\n", cps->which);
14106         InterruptChildProcess(cps->pr);
14107         cps->maybeThinking = FALSE;
14108         break;
14109       default:
14110         break;
14111     }
14112 #endif /*ATTENTION*/
14113 }
14114
14115 int
14116 CheckFlags()
14117 {
14118     if (whiteTimeRemaining <= 0) {
14119         if (!whiteFlag) {
14120             whiteFlag = TRUE;
14121             if (appData.icsActive) {
14122                 if (appData.autoCallFlag &&
14123                     gameMode == IcsPlayingBlack && !blackFlag) {
14124                   SendToICS(ics_prefix);
14125                   SendToICS("flag\n");
14126                 }
14127             } else {
14128                 if (blackFlag) {
14129                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14130                 } else {
14131                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14132                     if (appData.autoCallFlag) {
14133                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14134                         return TRUE;
14135                     }
14136                 }
14137             }
14138         }
14139     }
14140     if (blackTimeRemaining <= 0) {
14141         if (!blackFlag) {
14142             blackFlag = TRUE;
14143             if (appData.icsActive) {
14144                 if (appData.autoCallFlag &&
14145                     gameMode == IcsPlayingWhite && !whiteFlag) {
14146                   SendToICS(ics_prefix);
14147                   SendToICS("flag\n");
14148                 }
14149             } else {
14150                 if (whiteFlag) {
14151                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14152                 } else {
14153                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14154                     if (appData.autoCallFlag) {
14155                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14156                         return TRUE;
14157                     }
14158                 }
14159             }
14160         }
14161     }
14162     return FALSE;
14163 }
14164
14165 void
14166 CheckTimeControl()
14167 {
14168     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14169         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14170
14171     /*
14172      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14173      */
14174     if ( !WhiteOnMove(forwardMostMove) ) {
14175         /* White made time control */
14176         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14177         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14178         /* [HGM] time odds: correct new time quota for time odds! */
14179                                             / WhitePlayer()->timeOdds;
14180         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14181     } else {
14182         lastBlack -= blackTimeRemaining;
14183         /* Black made time control */
14184         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14185                                             / WhitePlayer()->other->timeOdds;
14186         lastWhite = whiteTimeRemaining;
14187     }
14188 }
14189
14190 void
14191 DisplayBothClocks()
14192 {
14193     int wom = gameMode == EditPosition ?
14194       !blackPlaysFirst : WhiteOnMove(currentMove);
14195     DisplayWhiteClock(whiteTimeRemaining, wom);
14196     DisplayBlackClock(blackTimeRemaining, !wom);
14197 }
14198
14199
14200 /* Timekeeping seems to be a portability nightmare.  I think everyone
14201    has ftime(), but I'm really not sure, so I'm including some ifdefs
14202    to use other calls if you don't.  Clocks will be less accurate if
14203    you have neither ftime nor gettimeofday.
14204 */
14205
14206 /* VS 2008 requires the #include outside of the function */
14207 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14208 #include <sys/timeb.h>
14209 #endif
14210
14211 /* Get the current time as a TimeMark */
14212 void
14213 GetTimeMark(tm)
14214      TimeMark *tm;
14215 {
14216 #if HAVE_GETTIMEOFDAY
14217
14218     struct timeval timeVal;
14219     struct timezone timeZone;
14220
14221     gettimeofday(&timeVal, &timeZone);
14222     tm->sec = (long) timeVal.tv_sec;
14223     tm->ms = (int) (timeVal.tv_usec / 1000L);
14224
14225 #else /*!HAVE_GETTIMEOFDAY*/
14226 #if HAVE_FTIME
14227
14228 // include <sys/timeb.h> / moved to just above start of function
14229     struct timeb timeB;
14230
14231     ftime(&timeB);
14232     tm->sec = (long) timeB.time;
14233     tm->ms = (int) timeB.millitm;
14234
14235 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14236     tm->sec = (long) time(NULL);
14237     tm->ms = 0;
14238 #endif
14239 #endif
14240 }
14241
14242 /* Return the difference in milliseconds between two
14243    time marks.  We assume the difference will fit in a long!
14244 */
14245 long
14246 SubtractTimeMarks(tm2, tm1)
14247      TimeMark *tm2, *tm1;
14248 {
14249     return 1000L*(tm2->sec - tm1->sec) +
14250            (long) (tm2->ms - tm1->ms);
14251 }
14252
14253
14254 /*
14255  * Code to manage the game clocks.
14256  *
14257  * In tournament play, black starts the clock and then white makes a move.
14258  * We give the human user a slight advantage if he is playing white---the
14259  * clocks don't run until he makes his first move, so it takes zero time.
14260  * Also, we don't account for network lag, so we could get out of sync
14261  * with GNU Chess's clock -- but then, referees are always right.
14262  */
14263
14264 static TimeMark tickStartTM;
14265 static long intendedTickLength;
14266
14267 long
14268 NextTickLength(timeRemaining)
14269      long timeRemaining;
14270 {
14271     long nominalTickLength, nextTickLength;
14272
14273     if (timeRemaining > 0L && timeRemaining <= 10000L)
14274       nominalTickLength = 100L;
14275     else
14276       nominalTickLength = 1000L;
14277     nextTickLength = timeRemaining % nominalTickLength;
14278     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14279
14280     return nextTickLength;
14281 }
14282
14283 /* Adjust clock one minute up or down */
14284 void
14285 AdjustClock(Boolean which, int dir)
14286 {
14287     if(which) blackTimeRemaining += 60000*dir;
14288     else      whiteTimeRemaining += 60000*dir;
14289     DisplayBothClocks();
14290 }
14291
14292 /* Stop clocks and reset to a fresh time control */
14293 void
14294 ResetClocks()
14295 {
14296     (void) StopClockTimer();
14297     if (appData.icsActive) {
14298         whiteTimeRemaining = blackTimeRemaining = 0;
14299     } else if (searchTime) {
14300         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14301         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14302     } else { /* [HGM] correct new time quote for time odds */
14303         whiteTC = blackTC = fullTimeControlString;
14304         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14305         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14306     }
14307     if (whiteFlag || blackFlag) {
14308         DisplayTitle("");
14309         whiteFlag = blackFlag = FALSE;
14310     }
14311     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14312     DisplayBothClocks();
14313 }
14314
14315 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14316
14317 /* Decrement running clock by amount of time that has passed */
14318 void
14319 DecrementClocks()
14320 {
14321     long timeRemaining;
14322     long lastTickLength, fudge;
14323     TimeMark now;
14324
14325     if (!appData.clockMode) return;
14326     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14327
14328     GetTimeMark(&now);
14329
14330     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14331
14332     /* Fudge if we woke up a little too soon */
14333     fudge = intendedTickLength - lastTickLength;
14334     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14335
14336     if (WhiteOnMove(forwardMostMove)) {
14337         if(whiteNPS >= 0) lastTickLength = 0;
14338         timeRemaining = whiteTimeRemaining -= lastTickLength;
14339         if(timeRemaining < 0 && !appData.icsActive) {
14340             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14341             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14342                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14343                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14344             }
14345         }
14346         DisplayWhiteClock(whiteTimeRemaining - fudge,
14347                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14348     } else {
14349         if(blackNPS >= 0) lastTickLength = 0;
14350         timeRemaining = blackTimeRemaining -= lastTickLength;
14351         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14352             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14353             if(suddenDeath) {
14354                 blackStartMove = forwardMostMove;
14355                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14356             }
14357         }
14358         DisplayBlackClock(blackTimeRemaining - fudge,
14359                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14360     }
14361     if (CheckFlags()) return;
14362
14363     tickStartTM = now;
14364     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14365     StartClockTimer(intendedTickLength);
14366
14367     /* if the time remaining has fallen below the alarm threshold, sound the
14368      * alarm. if the alarm has sounded and (due to a takeback or time control
14369      * with increment) the time remaining has increased to a level above the
14370      * threshold, reset the alarm so it can sound again.
14371      */
14372
14373     if (appData.icsActive && appData.icsAlarm) {
14374
14375         /* make sure we are dealing with the user's clock */
14376         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14377                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14378            )) return;
14379
14380         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14381             alarmSounded = FALSE;
14382         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14383             PlayAlarmSound();
14384             alarmSounded = TRUE;
14385         }
14386     }
14387 }
14388
14389
14390 /* A player has just moved, so stop the previously running
14391    clock and (if in clock mode) start the other one.
14392    We redisplay both clocks in case we're in ICS mode, because
14393    ICS gives us an update to both clocks after every move.
14394    Note that this routine is called *after* forwardMostMove
14395    is updated, so the last fractional tick must be subtracted
14396    from the color that is *not* on move now.
14397 */
14398 void
14399 SwitchClocks(int newMoveNr)
14400 {
14401     long lastTickLength;
14402     TimeMark now;
14403     int flagged = FALSE;
14404
14405     GetTimeMark(&now);
14406
14407     if (StopClockTimer() && appData.clockMode) {
14408         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14409         if (!WhiteOnMove(forwardMostMove)) {
14410             if(blackNPS >= 0) lastTickLength = 0;
14411             blackTimeRemaining -= lastTickLength;
14412            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14413 //         if(pvInfoList[forwardMostMove-1].time == -1)
14414                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14415                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14416         } else {
14417            if(whiteNPS >= 0) lastTickLength = 0;
14418            whiteTimeRemaining -= lastTickLength;
14419            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14420 //         if(pvInfoList[forwardMostMove-1].time == -1)
14421                  pvInfoList[forwardMostMove-1].time =
14422                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14423         }
14424         flagged = CheckFlags();
14425     }
14426     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14427     CheckTimeControl();
14428
14429     if (flagged || !appData.clockMode) return;
14430
14431     switch (gameMode) {
14432       case MachinePlaysBlack:
14433       case MachinePlaysWhite:
14434       case BeginningOfGame:
14435         if (pausing) return;
14436         break;
14437
14438       case EditGame:
14439       case PlayFromGameFile:
14440       case IcsExamining:
14441         return;
14442
14443       default:
14444         break;
14445     }
14446
14447     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14448         if(WhiteOnMove(forwardMostMove))
14449              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14450         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14451     }
14452
14453     tickStartTM = now;
14454     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14455       whiteTimeRemaining : blackTimeRemaining);
14456     StartClockTimer(intendedTickLength);
14457 }
14458
14459
14460 /* Stop both clocks */
14461 void
14462 StopClocks()
14463 {
14464     long lastTickLength;
14465     TimeMark now;
14466
14467     if (!StopClockTimer()) return;
14468     if (!appData.clockMode) return;
14469
14470     GetTimeMark(&now);
14471
14472     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14473     if (WhiteOnMove(forwardMostMove)) {
14474         if(whiteNPS >= 0) lastTickLength = 0;
14475         whiteTimeRemaining -= lastTickLength;
14476         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14477     } else {
14478         if(blackNPS >= 0) lastTickLength = 0;
14479         blackTimeRemaining -= lastTickLength;
14480         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14481     }
14482     CheckFlags();
14483 }
14484
14485 /* Start clock of player on move.  Time may have been reset, so
14486    if clock is already running, stop and restart it. */
14487 void
14488 StartClocks()
14489 {
14490     (void) StopClockTimer(); /* in case it was running already */
14491     DisplayBothClocks();
14492     if (CheckFlags()) return;
14493
14494     if (!appData.clockMode) return;
14495     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14496
14497     GetTimeMark(&tickStartTM);
14498     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14499       whiteTimeRemaining : blackTimeRemaining);
14500
14501    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14502     whiteNPS = blackNPS = -1;
14503     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14504        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14505         whiteNPS = first.nps;
14506     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14507        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14508         blackNPS = first.nps;
14509     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14510         whiteNPS = second.nps;
14511     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14512         blackNPS = second.nps;
14513     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14514
14515     StartClockTimer(intendedTickLength);
14516 }
14517
14518 char *
14519 TimeString(ms)
14520      long ms;
14521 {
14522     long second, minute, hour, day;
14523     char *sign = "";
14524     static char buf[32];
14525
14526     if (ms > 0 && ms <= 9900) {
14527       /* convert milliseconds to tenths, rounding up */
14528       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14529
14530       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14531       return buf;
14532     }
14533
14534     /* convert milliseconds to seconds, rounding up */
14535     /* use floating point to avoid strangeness of integer division
14536        with negative dividends on many machines */
14537     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14538
14539     if (second < 0) {
14540         sign = "-";
14541         second = -second;
14542     }
14543
14544     day = second / (60 * 60 * 24);
14545     second = second % (60 * 60 * 24);
14546     hour = second / (60 * 60);
14547     second = second % (60 * 60);
14548     minute = second / 60;
14549     second = second % 60;
14550
14551     if (day > 0)
14552       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14553               sign, day, hour, minute, second);
14554     else if (hour > 0)
14555       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14556     else
14557       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14558
14559     return buf;
14560 }
14561
14562
14563 /*
14564  * This is necessary because some C libraries aren't ANSI C compliant yet.
14565  */
14566 char *
14567 StrStr(string, match)
14568      char *string, *match;
14569 {
14570     int i, length;
14571
14572     length = strlen(match);
14573
14574     for (i = strlen(string) - length; i >= 0; i--, string++)
14575       if (!strncmp(match, string, length))
14576         return string;
14577
14578     return NULL;
14579 }
14580
14581 char *
14582 StrCaseStr(string, match)
14583      char *string, *match;
14584 {
14585     int i, j, length;
14586
14587     length = strlen(match);
14588
14589     for (i = strlen(string) - length; i >= 0; i--, string++) {
14590         for (j = 0; j < length; j++) {
14591             if (ToLower(match[j]) != ToLower(string[j]))
14592               break;
14593         }
14594         if (j == length) return string;
14595     }
14596
14597     return NULL;
14598 }
14599
14600 #ifndef _amigados
14601 int
14602 StrCaseCmp(s1, s2)
14603      char *s1, *s2;
14604 {
14605     char c1, c2;
14606
14607     for (;;) {
14608         c1 = ToLower(*s1++);
14609         c2 = ToLower(*s2++);
14610         if (c1 > c2) return 1;
14611         if (c1 < c2) return -1;
14612         if (c1 == NULLCHAR) return 0;
14613     }
14614 }
14615
14616
14617 int
14618 ToLower(c)
14619      int c;
14620 {
14621     return isupper(c) ? tolower(c) : c;
14622 }
14623
14624
14625 int
14626 ToUpper(c)
14627      int c;
14628 {
14629     return islower(c) ? toupper(c) : c;
14630 }
14631 #endif /* !_amigados    */
14632
14633 char *
14634 StrSave(s)
14635      char *s;
14636 {
14637   char *ret;
14638
14639   if ((ret = (char *) malloc(strlen(s) + 1)))
14640     {
14641       safeStrCpy(ret, s, strlen(s)+1);
14642     }
14643   return ret;
14644 }
14645
14646 char *
14647 StrSavePtr(s, savePtr)
14648      char *s, **savePtr;
14649 {
14650     if (*savePtr) {
14651         free(*savePtr);
14652     }
14653     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14654       safeStrCpy(*savePtr, s, strlen(s)+1);
14655     }
14656     return(*savePtr);
14657 }
14658
14659 char *
14660 PGNDate()
14661 {
14662     time_t clock;
14663     struct tm *tm;
14664     char buf[MSG_SIZ];
14665
14666     clock = time((time_t *)NULL);
14667     tm = localtime(&clock);
14668     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14669             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14670     return StrSave(buf);
14671 }
14672
14673
14674 char *
14675 PositionToFEN(move, overrideCastling)
14676      int move;
14677      char *overrideCastling;
14678 {
14679     int i, j, fromX, fromY, toX, toY;
14680     int whiteToPlay;
14681     char buf[128];
14682     char *p, *q;
14683     int emptycount;
14684     ChessSquare piece;
14685
14686     whiteToPlay = (gameMode == EditPosition) ?
14687       !blackPlaysFirst : (move % 2 == 0);
14688     p = buf;
14689
14690     /* Piece placement data */
14691     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14692         emptycount = 0;
14693         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14694             if (boards[move][i][j] == EmptySquare) {
14695                 emptycount++;
14696             } else { ChessSquare piece = boards[move][i][j];
14697                 if (emptycount > 0) {
14698                     if(emptycount<10) /* [HGM] can be >= 10 */
14699                         *p++ = '0' + emptycount;
14700                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14701                     emptycount = 0;
14702                 }
14703                 if(PieceToChar(piece) == '+') {
14704                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14705                     *p++ = '+';
14706                     piece = (ChessSquare)(DEMOTED piece);
14707                 }
14708                 *p++ = PieceToChar(piece);
14709                 if(p[-1] == '~') {
14710                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14711                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14712                     *p++ = '~';
14713                 }
14714             }
14715         }
14716         if (emptycount > 0) {
14717             if(emptycount<10) /* [HGM] can be >= 10 */
14718                 *p++ = '0' + emptycount;
14719             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14720             emptycount = 0;
14721         }
14722         *p++ = '/';
14723     }
14724     *(p - 1) = ' ';
14725
14726     /* [HGM] print Crazyhouse or Shogi holdings */
14727     if( gameInfo.holdingsWidth ) {
14728         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14729         q = p;
14730         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14731             piece = boards[move][i][BOARD_WIDTH-1];
14732             if( piece != EmptySquare )
14733               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14734                   *p++ = PieceToChar(piece);
14735         }
14736         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14737             piece = boards[move][BOARD_HEIGHT-i-1][0];
14738             if( piece != EmptySquare )
14739               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14740                   *p++ = PieceToChar(piece);
14741         }
14742
14743         if( q == p ) *p++ = '-';
14744         *p++ = ']';
14745         *p++ = ' ';
14746     }
14747
14748     /* Active color */
14749     *p++ = whiteToPlay ? 'w' : 'b';
14750     *p++ = ' ';
14751
14752   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14753     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14754   } else {
14755   if(nrCastlingRights) {
14756      q = p;
14757      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14758        /* [HGM] write directly from rights */
14759            if(boards[move][CASTLING][2] != NoRights &&
14760               boards[move][CASTLING][0] != NoRights   )
14761                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14762            if(boards[move][CASTLING][2] != NoRights &&
14763               boards[move][CASTLING][1] != NoRights   )
14764                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14765            if(boards[move][CASTLING][5] != NoRights &&
14766               boards[move][CASTLING][3] != NoRights   )
14767                 *p++ = boards[move][CASTLING][3] + AAA;
14768            if(boards[move][CASTLING][5] != NoRights &&
14769               boards[move][CASTLING][4] != NoRights   )
14770                 *p++ = boards[move][CASTLING][4] + AAA;
14771      } else {
14772
14773         /* [HGM] write true castling rights */
14774         if( nrCastlingRights == 6 ) {
14775             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14776                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14777             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14778                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14779             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14780                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14781             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14782                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14783         }
14784      }
14785      if (q == p) *p++ = '-'; /* No castling rights */
14786      *p++ = ' ';
14787   }
14788
14789   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14790      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14791     /* En passant target square */
14792     if (move > backwardMostMove) {
14793         fromX = moveList[move - 1][0] - AAA;
14794         fromY = moveList[move - 1][1] - ONE;
14795         toX = moveList[move - 1][2] - AAA;
14796         toY = moveList[move - 1][3] - ONE;
14797         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14798             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14799             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14800             fromX == toX) {
14801             /* 2-square pawn move just happened */
14802             *p++ = toX + AAA;
14803             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14804         } else {
14805             *p++ = '-';
14806         }
14807     } else if(move == backwardMostMove) {
14808         // [HGM] perhaps we should always do it like this, and forget the above?
14809         if((signed char)boards[move][EP_STATUS] >= 0) {
14810             *p++ = boards[move][EP_STATUS] + AAA;
14811             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14812         } else {
14813             *p++ = '-';
14814         }
14815     } else {
14816         *p++ = '-';
14817     }
14818     *p++ = ' ';
14819   }
14820   }
14821
14822     /* [HGM] find reversible plies */
14823     {   int i = 0, j=move;
14824
14825         if (appData.debugMode) { int k;
14826             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14827             for(k=backwardMostMove; k<=forwardMostMove; k++)
14828                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14829
14830         }
14831
14832         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14833         if( j == backwardMostMove ) i += initialRulePlies;
14834         sprintf(p, "%d ", i);
14835         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14836     }
14837     /* Fullmove number */
14838     sprintf(p, "%d", (move / 2) + 1);
14839
14840     return StrSave(buf);
14841 }
14842
14843 Boolean
14844 ParseFEN(board, blackPlaysFirst, fen)
14845     Board board;
14846      int *blackPlaysFirst;
14847      char *fen;
14848 {
14849     int i, j;
14850     char *p, c;
14851     int emptycount;
14852     ChessSquare piece;
14853
14854     p = fen;
14855
14856     /* [HGM] by default clear Crazyhouse holdings, if present */
14857     if(gameInfo.holdingsWidth) {
14858        for(i=0; i<BOARD_HEIGHT; i++) {
14859            board[i][0]             = EmptySquare; /* black holdings */
14860            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14861            board[i][1]             = (ChessSquare) 0; /* black counts */
14862            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14863        }
14864     }
14865
14866     /* Piece placement data */
14867     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14868         j = 0;
14869         for (;;) {
14870             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14871                 if (*p == '/') p++;
14872                 emptycount = gameInfo.boardWidth - j;
14873                 while (emptycount--)
14874                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14875                 break;
14876 #if(BOARD_FILES >= 10)
14877             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14878                 p++; emptycount=10;
14879                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14880                 while (emptycount--)
14881                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14882 #endif
14883             } else if (isdigit(*p)) {
14884                 emptycount = *p++ - '0';
14885                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14886                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14887                 while (emptycount--)
14888                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14889             } else if (*p == '+' || isalpha(*p)) {
14890                 if (j >= gameInfo.boardWidth) return FALSE;
14891                 if(*p=='+') {
14892                     piece = CharToPiece(*++p);
14893                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14894                     piece = (ChessSquare) (PROMOTED piece ); p++;
14895                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14896                 } else piece = CharToPiece(*p++);
14897
14898                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14899                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14900                     piece = (ChessSquare) (PROMOTED piece);
14901                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14902                     p++;
14903                 }
14904                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14905             } else {
14906                 return FALSE;
14907             }
14908         }
14909     }
14910     while (*p == '/' || *p == ' ') p++;
14911
14912     /* [HGM] look for Crazyhouse holdings here */
14913     while(*p==' ') p++;
14914     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14915         if(*p == '[') p++;
14916         if(*p == '-' ) p++; /* empty holdings */ else {
14917             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14918             /* if we would allow FEN reading to set board size, we would   */
14919             /* have to add holdings and shift the board read so far here   */
14920             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14921                 p++;
14922                 if((int) piece >= (int) BlackPawn ) {
14923                     i = (int)piece - (int)BlackPawn;
14924                     i = PieceToNumber((ChessSquare)i);
14925                     if( i >= gameInfo.holdingsSize ) return FALSE;
14926                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14927                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14928                 } else {
14929                     i = (int)piece - (int)WhitePawn;
14930                     i = PieceToNumber((ChessSquare)i);
14931                     if( i >= gameInfo.holdingsSize ) return FALSE;
14932                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14933                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14934                 }
14935             }
14936         }
14937         if(*p == ']') p++;
14938     }
14939
14940     while(*p == ' ') p++;
14941
14942     /* Active color */
14943     c = *p++;
14944     if(appData.colorNickNames) {
14945       if( c == appData.colorNickNames[0] ) c = 'w'; else
14946       if( c == appData.colorNickNames[1] ) c = 'b';
14947     }
14948     switch (c) {
14949       case 'w':
14950         *blackPlaysFirst = FALSE;
14951         break;
14952       case 'b':
14953         *blackPlaysFirst = TRUE;
14954         break;
14955       default:
14956         return FALSE;
14957     }
14958
14959     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14960     /* return the extra info in global variiables             */
14961
14962     /* set defaults in case FEN is incomplete */
14963     board[EP_STATUS] = EP_UNKNOWN;
14964     for(i=0; i<nrCastlingRights; i++ ) {
14965         board[CASTLING][i] =
14966             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14967     }   /* assume possible unless obviously impossible */
14968     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14969     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14970     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14971                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14972     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14973     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14974     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14975                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14976     FENrulePlies = 0;
14977
14978     while(*p==' ') p++;
14979     if(nrCastlingRights) {
14980       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14981           /* castling indicator present, so default becomes no castlings */
14982           for(i=0; i<nrCastlingRights; i++ ) {
14983                  board[CASTLING][i] = NoRights;
14984           }
14985       }
14986       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14987              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14988              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14989              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14990         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14991
14992         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14993             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14994             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14995         }
14996         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14997             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14998         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14999                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15000         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15001                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15002         switch(c) {
15003           case'K':
15004               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15005               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15006               board[CASTLING][2] = whiteKingFile;
15007               break;
15008           case'Q':
15009               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15010               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15011               board[CASTLING][2] = whiteKingFile;
15012               break;
15013           case'k':
15014               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15015               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15016               board[CASTLING][5] = blackKingFile;
15017               break;
15018           case'q':
15019               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15020               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15021               board[CASTLING][5] = blackKingFile;
15022           case '-':
15023               break;
15024           default: /* FRC castlings */
15025               if(c >= 'a') { /* black rights */
15026                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15027                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15028                   if(i == BOARD_RGHT) break;
15029                   board[CASTLING][5] = i;
15030                   c -= AAA;
15031                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15032                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15033                   if(c > i)
15034                       board[CASTLING][3] = c;
15035                   else
15036                       board[CASTLING][4] = c;
15037               } else { /* white rights */
15038                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15039                     if(board[0][i] == WhiteKing) break;
15040                   if(i == BOARD_RGHT) break;
15041                   board[CASTLING][2] = i;
15042                   c -= AAA - 'a' + 'A';
15043                   if(board[0][c] >= WhiteKing) break;
15044                   if(c > i)
15045                       board[CASTLING][0] = c;
15046                   else
15047                       board[CASTLING][1] = c;
15048               }
15049         }
15050       }
15051       for(i=0; i<nrCastlingRights; i++)
15052         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15053     if (appData.debugMode) {
15054         fprintf(debugFP, "FEN castling rights:");
15055         for(i=0; i<nrCastlingRights; i++)
15056         fprintf(debugFP, " %d", board[CASTLING][i]);
15057         fprintf(debugFP, "\n");
15058     }
15059
15060       while(*p==' ') p++;
15061     }
15062
15063     /* read e.p. field in games that know e.p. capture */
15064     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15065        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15066       if(*p=='-') {
15067         p++; board[EP_STATUS] = EP_NONE;
15068       } else {
15069          char c = *p++ - AAA;
15070
15071          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15072          if(*p >= '0' && *p <='9') p++;
15073          board[EP_STATUS] = c;
15074       }
15075     }
15076
15077
15078     if(sscanf(p, "%d", &i) == 1) {
15079         FENrulePlies = i; /* 50-move ply counter */
15080         /* (The move number is still ignored)    */
15081     }
15082
15083     return TRUE;
15084 }
15085
15086 void
15087 EditPositionPasteFEN(char *fen)
15088 {
15089   if (fen != NULL) {
15090     Board initial_position;
15091
15092     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15093       DisplayError(_("Bad FEN position in clipboard"), 0);
15094       return ;
15095     } else {
15096       int savedBlackPlaysFirst = blackPlaysFirst;
15097       EditPositionEvent();
15098       blackPlaysFirst = savedBlackPlaysFirst;
15099       CopyBoard(boards[0], initial_position);
15100       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15101       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15102       DisplayBothClocks();
15103       DrawPosition(FALSE, boards[currentMove]);
15104     }
15105   }
15106 }
15107
15108 static char cseq[12] = "\\   ";
15109
15110 Boolean set_cont_sequence(char *new_seq)
15111 {
15112     int len;
15113     Boolean ret;
15114
15115     // handle bad attempts to set the sequence
15116         if (!new_seq)
15117                 return 0; // acceptable error - no debug
15118
15119     len = strlen(new_seq);
15120     ret = (len > 0) && (len < sizeof(cseq));
15121     if (ret)
15122       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15123     else if (appData.debugMode)
15124       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15125     return ret;
15126 }
15127
15128 /*
15129     reformat a source message so words don't cross the width boundary.  internal
15130     newlines are not removed.  returns the wrapped size (no null character unless
15131     included in source message).  If dest is NULL, only calculate the size required
15132     for the dest buffer.  lp argument indicats line position upon entry, and it's
15133     passed back upon exit.
15134 */
15135 int wrap(char *dest, char *src, int count, int width, int *lp)
15136 {
15137     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15138
15139     cseq_len = strlen(cseq);
15140     old_line = line = *lp;
15141     ansi = len = clen = 0;
15142
15143     for (i=0; i < count; i++)
15144     {
15145         if (src[i] == '\033')
15146             ansi = 1;
15147
15148         // if we hit the width, back up
15149         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15150         {
15151             // store i & len in case the word is too long
15152             old_i = i, old_len = len;
15153
15154             // find the end of the last word
15155             while (i && src[i] != ' ' && src[i] != '\n')
15156             {
15157                 i--;
15158                 len--;
15159             }
15160
15161             // word too long?  restore i & len before splitting it
15162             if ((old_i-i+clen) >= width)
15163             {
15164                 i = old_i;
15165                 len = old_len;
15166             }
15167
15168             // extra space?
15169             if (i && src[i-1] == ' ')
15170                 len--;
15171
15172             if (src[i] != ' ' && src[i] != '\n')
15173             {
15174                 i--;
15175                 if (len)
15176                     len--;
15177             }
15178
15179             // now append the newline and continuation sequence
15180             if (dest)
15181                 dest[len] = '\n';
15182             len++;
15183             if (dest)
15184                 strncpy(dest+len, cseq, cseq_len);
15185             len += cseq_len;
15186             line = cseq_len;
15187             clen = cseq_len;
15188             continue;
15189         }
15190
15191         if (dest)
15192             dest[len] = src[i];
15193         len++;
15194         if (!ansi)
15195             line++;
15196         if (src[i] == '\n')
15197             line = 0;
15198         if (src[i] == 'm')
15199             ansi = 0;
15200     }
15201     if (dest && appData.debugMode)
15202     {
15203         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15204             count, width, line, len, *lp);
15205         show_bytes(debugFP, src, count);
15206         fprintf(debugFP, "\ndest: ");
15207         show_bytes(debugFP, dest, len);
15208         fprintf(debugFP, "\n");
15209     }
15210     *lp = dest ? line : old_line;
15211
15212     return len;
15213 }
15214
15215 // [HGM] vari: routines for shelving variations
15216
15217 void
15218 PushTail(int firstMove, int lastMove)
15219 {
15220         int i, j, nrMoves = lastMove - firstMove;
15221
15222         if(appData.icsActive) { // only in local mode
15223                 forwardMostMove = currentMove; // mimic old ICS behavior
15224                 return;
15225         }
15226         if(storedGames >= MAX_VARIATIONS-1) return;
15227
15228         // push current tail of game on stack
15229         savedResult[storedGames] = gameInfo.result;
15230         savedDetails[storedGames] = gameInfo.resultDetails;
15231         gameInfo.resultDetails = NULL;
15232         savedFirst[storedGames] = firstMove;
15233         savedLast [storedGames] = lastMove;
15234         savedFramePtr[storedGames] = framePtr;
15235         framePtr -= nrMoves; // reserve space for the boards
15236         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15237             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15238             for(j=0; j<MOVE_LEN; j++)
15239                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15240             for(j=0; j<2*MOVE_LEN; j++)
15241                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15242             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15243             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15244             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15245             pvInfoList[firstMove+i-1].depth = 0;
15246             commentList[framePtr+i] = commentList[firstMove+i];
15247             commentList[firstMove+i] = NULL;
15248         }
15249
15250         storedGames++;
15251         forwardMostMove = firstMove; // truncate game so we can start variation
15252         if(storedGames == 1) GreyRevert(FALSE);
15253 }
15254
15255 Boolean
15256 PopTail(Boolean annotate)
15257 {
15258         int i, j, nrMoves;
15259         char buf[8000], moveBuf[20];
15260
15261         if(appData.icsActive) return FALSE; // only in local mode
15262         if(!storedGames) return FALSE; // sanity
15263         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15264
15265         storedGames--;
15266         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15267         nrMoves = savedLast[storedGames] - currentMove;
15268         if(annotate) {
15269                 int cnt = 10;
15270                 if(!WhiteOnMove(currentMove))
15271                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15272                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15273                 for(i=currentMove; i<forwardMostMove; i++) {
15274                         if(WhiteOnMove(i))
15275                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15276                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15277                         strcat(buf, moveBuf);
15278                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15279                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15280                 }
15281                 strcat(buf, ")");
15282         }
15283         for(i=1; i<=nrMoves; i++) { // copy last variation back
15284             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15285             for(j=0; j<MOVE_LEN; j++)
15286                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15287             for(j=0; j<2*MOVE_LEN; j++)
15288                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15289             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15290             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15291             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15292             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15293             commentList[currentMove+i] = commentList[framePtr+i];
15294             commentList[framePtr+i] = NULL;
15295         }
15296         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15297         framePtr = savedFramePtr[storedGames];
15298         gameInfo.result = savedResult[storedGames];
15299         if(gameInfo.resultDetails != NULL) {
15300             free(gameInfo.resultDetails);
15301       }
15302         gameInfo.resultDetails = savedDetails[storedGames];
15303         forwardMostMove = currentMove + nrMoves;
15304         if(storedGames == 0) GreyRevert(TRUE);
15305         return TRUE;
15306 }
15307
15308 void
15309 CleanupTail()
15310 {       // remove all shelved variations
15311         int i;
15312         for(i=0; i<storedGames; i++) {
15313             if(savedDetails[i])
15314                 free(savedDetails[i]);
15315             savedDetails[i] = NULL;
15316         }
15317         for(i=framePtr; i<MAX_MOVES; i++) {
15318                 if(commentList[i]) free(commentList[i]);
15319                 commentList[i] = NULL;
15320         }
15321         framePtr = MAX_MOVES-1;
15322         storedGames = 0;
15323 }
15324
15325 void
15326 LoadVariation(int index, char *text)
15327 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15328         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15329         int level = 0, move;
15330
15331         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15332         // first find outermost bracketing variation
15333         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15334             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15335                 if(*p == '{') wait = '}'; else
15336                 if(*p == '[') wait = ']'; else
15337                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15338                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15339             }
15340             if(*p == wait) wait = NULLCHAR; // closing ]} found
15341             p++;
15342         }
15343         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15344         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15345         end[1] = NULLCHAR; // clip off comment beyond variation
15346         ToNrEvent(currentMove-1);
15347         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15348         // kludge: use ParsePV() to append variation to game
15349         move = currentMove;
15350         ParsePV(start, TRUE);
15351         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15352         ClearPremoveHighlights();
15353         CommentPopDown();
15354         ToNrEvent(currentMove+1);
15355 }
15356