Limit multi-session clock handling to non-ICS games
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
17  * Permission to use, copy, modify, and distribute this software and its
18  * documentation for any purpose and without fee is hereby granted,
19  * provided that the above copyright notice appear in all copies and that
20  * both that copyright notice and this permission notice appear in
21  * supporting documentation, and that the name of Digital not be
22  * used in advertising or publicity pertaining to distribution of the
23  * software without specific, written prior permission.
24  *
25  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
38  * GNU XBoard is free software: you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation, either version 3 of the License, or (at
41  * your option) any later version.
42  *
43  * GNU XBoard is distributed in the hope that it will be useful, but
44  * WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46  * General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program. If not, see http://www.gnu.org/licenses/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
59
60 #else
61
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
63
64 #endif
65
66 #include "config.h"
67
68 #include <assert.h>
69 #include <stdio.h>
70 #include <ctype.h>
71 #include <errno.h>
72 #include <sys/types.h>
73 #include <sys/stat.h>
74 #include <math.h>
75 #include <ctype.h>
76
77 #if STDC_HEADERS
78 # include <stdlib.h>
79 # include <string.h>
80 # include <stdarg.h>
81 #else /* not STDC_HEADERS */
82 # if HAVE_STRING_H
83 #  include <string.h>
84 # else /* not HAVE_STRING_H */
85 #  include <strings.h>
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
88
89 #if HAVE_SYS_FCNTL_H
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
92 # if HAVE_FCNTL_H
93 #  include <fcntl.h>
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
96
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
99 # include <time.h>
100 #else
101 # if HAVE_SYS_TIME_H
102 #  include <sys/time.h>
103 # else
104 #  include <time.h>
105 # endif
106 #endif
107
108 #if defined(_amigados) && !defined(__GNUC__)
109 struct timezone {
110     int tz_minuteswest;
111     int tz_dsttime;
112 };
113 extern int gettimeofday(struct timeval *, struct timezone *);
114 #endif
115
116 #if HAVE_UNISTD_H
117 # include <unistd.h>
118 #endif
119
120 #include "common.h"
121 #include "frontend.h"
122 #include "backend.h"
123 #include "parser.h"
124 #include "moves.h"
125 #if ZIPPY
126 # include "zippy.h"
127 #endif
128 #include "backendz.h"
129 #include "gettext.h"
130
131 #ifdef ENABLE_NLS
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
135 #else
136 # ifdef WIN32
137 #   define _(s) T_(s)
138 #   define N_(s) s
139 # else
140 #   define _(s) (s)
141 #   define N_(s) s
142 #   define T_(s) s
143 # endif
144 #endif
145
146
147 /* A point in time */
148 typedef struct {
149     long sec;  /* Assuming this is >= 32 bits */
150     int ms;    /* Assuming this is >= 16 bits */
151 } TimeMark;
152
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155                          char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157                       char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
169                                                                                 Board board));
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173                    /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185                            char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187                         int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
194
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228
229 #ifdef WIN32
230        extern void ConsoleCreate();
231 #endif
232
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
236
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
244
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 int endPV = -1;
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
252 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
256 Boolean partnerUp;
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
268 int chattingPartner;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270
271 /* States for ics_getting_history */
272 #define H_FALSE 0
273 #define H_REQUESTED 1
274 #define H_GOT_REQ_HEADER 2
275 #define H_GOT_UNREQ_HEADER 3
276 #define H_GETTING_MOVES 4
277 #define H_GOT_UNWANTED_HEADER 5
278
279 /* whosays values for GameEnds */
280 #define GE_ICS 0
281 #define GE_ENGINE 1
282 #define GE_PLAYER 2
283 #define GE_FILE 3
284 #define GE_XBOARD 4
285 #define GE_ENGINE1 5
286 #define GE_ENGINE2 6
287
288 /* Maximum number of games in a cmail message */
289 #define CMAIL_MAX_GAMES 20
290
291 /* Different types of move when calling RegisterMove */
292 #define CMAIL_MOVE   0
293 #define CMAIL_RESIGN 1
294 #define CMAIL_DRAW   2
295 #define CMAIL_ACCEPT 3
296
297 /* Different types of result to remember for each game */
298 #define CMAIL_NOT_RESULT 0
299 #define CMAIL_OLD_RESULT 1
300 #define CMAIL_NEW_RESULT 2
301
302 /* Telnet protocol constants */
303 #define TN_WILL 0373
304 #define TN_WONT 0374
305 #define TN_DO   0375
306 #define TN_DONT 0376
307 #define TN_IAC  0377
308 #define TN_ECHO 0001
309 #define TN_SGA  0003
310 #define TN_PORT 23
311
312 char*
313 safeStrCpy( char *dst, const char *src, size_t count )
314 {
315   /* see for example: https://buildsecurityin.us-cert.gov/bsi-rules/home/g1/854-BSI.html
316    *
317    * usage:   safeStrCpy( stringA, stringB, sizeof(stringA)/sizeof(stringA[0]);
318    */
319
320   assert( dst != NULL );
321   assert( src != NULL );
322   assert( count > 0 );
323
324   strncpy( dst, src, count );
325   if(  dst[ count-1 ] != '\0' )
326     {
327       if(appData.debugMode)
328       printf("safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst,count);
329     }
330   dst[ count-1 ] = '\0';
331
332   return dst;
333 }
334
335 /* Some compiler can't cast u64 to double
336  * This function do the job for us:
337
338  * We use the highest bit for cast, this only
339  * works if the highest bit is not
340  * in use (This should not happen)
341  *
342  * We used this for all compiler
343  */
344 double
345 u64ToDouble(u64 value)
346 {
347   double r;
348   u64 tmp = value & u64Const(0x7fffffffffffffff);
349   r = (double)(s64)tmp;
350   if (value & u64Const(0x8000000000000000))
351        r +=  9.2233720368547758080e18; /* 2^63 */
352  return r;
353 }
354
355 /* Fake up flags for now, as we aren't keeping track of castling
356    availability yet. [HGM] Change of logic: the flag now only
357    indicates the type of castlings allowed by the rule of the game.
358    The actual rights themselves are maintained in the array
359    castlingRights, as part of the game history, and are not probed
360    by this function.
361  */
362 int
363 PosFlags(index)
364 {
365   int flags = F_ALL_CASTLE_OK;
366   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367   switch (gameInfo.variant) {
368   case VariantSuicide:
369     flags &= ~F_ALL_CASTLE_OK;
370   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371     flags |= F_IGNORE_CHECK;
372   case VariantLosers:
373     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374     break;
375   case VariantAtomic:
376     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377     break;
378   case VariantKriegspiel:
379     flags |= F_KRIEGSPIEL_CAPTURE;
380     break;
381   case VariantCapaRandom:
382   case VariantFischeRandom:
383     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384   case VariantNoCastle:
385   case VariantShatranj:
386   case VariantCourier:
387   case VariantMakruk:
388     flags &= ~F_ALL_CASTLE_OK;
389     break;
390   default:
391     break;
392   }
393   return flags;
394 }
395
396 FILE *gameFileFP, *debugFP;
397
398 /*
399     [AS] Note: sometimes, the sscanf() function is used to parse the input
400     into a fixed-size buffer. Because of this, we must be prepared to
401     receive strings as long as the size of the input buffer, which is currently
402     set to 4K for Windows and 8K for the rest.
403     So, we must either allocate sufficiently large buffers here, or
404     reduce the size of the input buffer in the input reading part.
405 */
406
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
410
411 ChessProgramState first, second;
412
413 /* premove variables */
414 int premoveToX = 0;
415 int premoveToY = 0;
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
419 int gotPremove = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
422
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
425
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452
453 int have_sent_ICS_logon = 0;
454 int sending_ICS_login    = 0;
455 int sending_ICS_password = 0;
456
457 int movesPerSession;
458 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
459 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0;
464 TimeMark programStartTime;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
467
468 /* animateTraining preserves the state of appData.animate
469  * when Training mode is activated. This allows the
470  * response to be animated when appData.animate == TRUE and
471  * appData.animateDragging == TRUE.
472  */
473 Boolean animateTraining;
474
475 GameInfo gameInfo;
476
477 AppData appData;
478
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char  initialRights[BOARD_FILES];
483 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int   initialRulePlies, FENrulePlies;
485 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 int loadFlag = 0;
487 int shuffleOpenings;
488 int mute; // mute all sounds
489
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int storedGames = 0;
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
499
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void CleanupTail P((void));
503
504 ChessSquare  FIDEArray[2][BOARD_FILES] = {
505     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
506         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
507     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
508         BlackKing, BlackBishop, BlackKnight, BlackRook }
509 };
510
511 ChessSquare twoKingsArray[2][BOARD_FILES] = {
512     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
514     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515         BlackKing, BlackKing, BlackKnight, BlackRook }
516 };
517
518 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
519     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
520         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
521     { BlackRook, BlackMan, BlackBishop, BlackQueen,
522         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
523 };
524
525 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
526     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
527         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
528     { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
529         BlackKing, BlackMarshall, BlackAlfil, BlackLance }
530 };
531
532 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
533     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
534         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
535     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
536         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
537 };
538
539 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
540     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
541         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
542     { BlackRook, BlackKnight, BlackMan, BlackFerz,
543         BlackKing, BlackMan, BlackKnight, BlackRook }
544 };
545
546
547 #if (BOARD_FILES>=10)
548 ChessSquare ShogiArray[2][BOARD_FILES] = {
549     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
550         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
551     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
552         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
553 };
554
555 ChessSquare XiangqiArray[2][BOARD_FILES] = {
556     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
557         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
558     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
559         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
560 };
561
562 ChessSquare CapablancaArray[2][BOARD_FILES] = {
563     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
564         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
565     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
566         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
567 };
568
569 ChessSquare GreatArray[2][BOARD_FILES] = {
570     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
571         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
572     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
573         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
574 };
575
576 ChessSquare JanusArray[2][BOARD_FILES] = {
577     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
578         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
579     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
580         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
581 };
582
583 #ifdef GOTHIC
584 ChessSquare GothicArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
586         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
587     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
588         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
589 };
590 #else // !GOTHIC
591 #define GothicArray CapablancaArray
592 #endif // !GOTHIC
593
594 #ifdef FALCON
595 ChessSquare FalconArray[2][BOARD_FILES] = {
596     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
597         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
598     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
599         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
600 };
601 #else // !FALCON
602 #define FalconArray CapablancaArray
603 #endif // !FALCON
604
605 #else // !(BOARD_FILES>=10)
606 #define XiangqiPosition FIDEArray
607 #define CapablancaArray FIDEArray
608 #define GothicArray FIDEArray
609 #define GreatArray FIDEArray
610 #endif // !(BOARD_FILES>=10)
611
612 #if (BOARD_FILES>=12)
613 ChessSquare CourierArray[2][BOARD_FILES] = {
614     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
615         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
616     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
617         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
618 };
619 #else // !(BOARD_FILES>=12)
620 #define CourierArray CapablancaArray
621 #endif // !(BOARD_FILES>=12)
622
623
624 Board initialPosition;
625
626
627 /* Convert str to a rating. Checks for special cases of "----",
628
629    "++++", etc. Also strips ()'s */
630 int
631 string_to_rating(str)
632   char *str;
633 {
634   while(*str && !isdigit(*str)) ++str;
635   if (!*str)
636     return 0;   /* One of the special "no rating" cases */
637   else
638     return atoi(str);
639 }
640
641 void
642 ClearProgramStats()
643 {
644     /* Init programStats */
645     programStats.movelist[0] = 0;
646     programStats.depth = 0;
647     programStats.nr_moves = 0;
648     programStats.moves_left = 0;
649     programStats.nodes = 0;
650     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
651     programStats.score = 0;
652     programStats.got_only_move = 0;
653     programStats.got_fail = 0;
654     programStats.line_is_book = 0;
655 }
656
657 void
658 InitBackEnd1()
659 {
660     int matched, min, sec;
661
662     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
663     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
664
665     GetTimeMark(&programStartTime);
666     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
667
668     ClearProgramStats();
669     programStats.ok_to_send = 1;
670     programStats.seen_stat = 0;
671
672     /*
673      * Initialize game list
674      */
675     ListNew(&gameList);
676
677
678     /*
679      * Internet chess server status
680      */
681     if (appData.icsActive) {
682         appData.matchMode = FALSE;
683         appData.matchGames = 0;
684 #if ZIPPY
685         appData.noChessProgram = !appData.zippyPlay;
686 #else
687         appData.zippyPlay = FALSE;
688         appData.zippyTalk = FALSE;
689         appData.noChessProgram = TRUE;
690 #endif
691         if (*appData.icsHelper != NULLCHAR) {
692             appData.useTelnet = TRUE;
693             appData.telnetProgram = appData.icsHelper;
694         }
695     } else {
696         appData.zippyTalk = appData.zippyPlay = FALSE;
697     }
698
699     /* [AS] Initialize pv info list [HGM] and game state */
700     {
701         int i, j;
702
703         for( i=0; i<=framePtr; i++ ) {
704             pvInfoList[i].depth = -1;
705             boards[i][EP_STATUS] = EP_NONE;
706             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
707         }
708     }
709
710     /*
711      * Parse timeControl resource
712      */
713     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
714                           appData.movesPerSession)) {
715         char buf[MSG_SIZ];
716         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
717         DisplayFatalError(buf, 0, 2);
718     }
719
720     /*
721      * Parse searchTime resource
722      */
723     if (*appData.searchTime != NULLCHAR) {
724         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
725         if (matched == 1) {
726             searchTime = min * 60;
727         } else if (matched == 2) {
728             searchTime = min * 60 + sec;
729         } else {
730             char buf[MSG_SIZ];
731             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
732             DisplayFatalError(buf, 0, 2);
733         }
734     }
735
736     /* [AS] Adjudication threshold */
737     adjudicateLossThreshold = appData.adjudicateLossThreshold;
738
739     first.which = _("first");
740     second.which = _("second");
741     first.maybeThinking = second.maybeThinking = FALSE;
742     first.pr = second.pr = NoProc;
743     first.isr = second.isr = NULL;
744     first.sendTime = second.sendTime = 2;
745     first.sendDrawOffers = 1;
746     if (appData.firstPlaysBlack) {
747         first.twoMachinesColor = "black\n";
748         second.twoMachinesColor = "white\n";
749     } else {
750         first.twoMachinesColor = "white\n";
751         second.twoMachinesColor = "black\n";
752     }
753     first.program = appData.firstChessProgram;
754     second.program = appData.secondChessProgram;
755     first.host = appData.firstHost;
756     second.host = appData.secondHost;
757     first.dir = appData.firstDirectory;
758     second.dir = appData.secondDirectory;
759     first.other = &second;
760     second.other = &first;
761     first.initString = appData.initString;
762     second.initString = appData.secondInitString;
763     first.computerString = appData.firstComputerString;
764     second.computerString = appData.secondComputerString;
765     first.useSigint = second.useSigint = TRUE;
766     first.useSigterm = second.useSigterm = TRUE;
767     first.reuse = appData.reuseFirst;
768     second.reuse = appData.reuseSecond;
769     first.nps = appData.firstNPS;   // [HGM] nps: copy nodes per second
770     second.nps = appData.secondNPS;
771     first.useSetboard = second.useSetboard = FALSE;
772     first.useSAN = second.useSAN = FALSE;
773     first.usePing = second.usePing = FALSE;
774     first.lastPing = second.lastPing = 0;
775     first.lastPong = second.lastPong = 0;
776     first.usePlayother = second.usePlayother = FALSE;
777     first.useColors = second.useColors = TRUE;
778     first.useUsermove = second.useUsermove = FALSE;
779     first.sendICS = second.sendICS = FALSE;
780     first.sendName = second.sendName = appData.icsActive;
781     first.sdKludge = second.sdKludge = FALSE;
782     first.stKludge = second.stKludge = FALSE;
783     TidyProgramName(first.program, first.host, first.tidy);
784     TidyProgramName(second.program, second.host, second.tidy);
785     first.matchWins = second.matchWins = 0;
786     safeStrCpy(first.variants, appData.variant, sizeof(first.variants)/sizeof(first.variants[0]));
787     safeStrCpy(second.variants, appData.variant,sizeof(second.variants)/sizeof(second.variants[0]));
788     first.analysisSupport = second.analysisSupport = 2; /* detect */
789     first.analyzing = second.analyzing = FALSE;
790     first.initDone = second.initDone = FALSE;
791
792     /* New features added by Tord: */
793     first.useFEN960 = FALSE; second.useFEN960 = FALSE;
794     first.useOOCastle = TRUE; second.useOOCastle = TRUE;
795     /* End of new features added by Tord. */
796     first.fenOverride  = appData.fenOverride1;
797     second.fenOverride = appData.fenOverride2;
798
799     /* [HGM] time odds: set factor for each machine */
800     first.timeOdds  = appData.firstTimeOdds;
801     second.timeOdds = appData.secondTimeOdds;
802     { float norm = 1;
803         if(appData.timeOddsMode) {
804             norm = first.timeOdds;
805             if(norm > second.timeOdds) norm = second.timeOdds;
806         }
807         first.timeOdds /= norm;
808         second.timeOdds /= norm;
809     }
810
811     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812     first.accumulateTC = appData.firstAccumulateTC;
813     second.accumulateTC = appData.secondAccumulateTC;
814     first.maxNrOfSessions = second.maxNrOfSessions = 1;
815
816     /* [HGM] debug */
817     first.debug = second.debug = FALSE;
818     first.supportsNPS = second.supportsNPS = UNKNOWN;
819
820     /* [HGM] options */
821     first.optionSettings  = appData.firstOptions;
822     second.optionSettings = appData.secondOptions;
823
824     first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
825     second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
826     first.isUCI = appData.firstIsUCI; /* [AS] */
827     second.isUCI = appData.secondIsUCI; /* [AS] */
828     first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
829     second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
830
831     if (appData.firstProtocolVersion > PROTOVER
832         || appData.firstProtocolVersion < 1)
833       {
834         char buf[MSG_SIZ];
835         int len;
836
837         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838                        appData.firstProtocolVersion);
839         if( (len > MSG_SIZ) && appData.debugMode )
840           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
841
842         DisplayFatalError(buf, 0, 2);
843       }
844     else
845       {
846         first.protocolVersion = appData.firstProtocolVersion;
847       }
848
849     if (appData.secondProtocolVersion > PROTOVER
850         || appData.secondProtocolVersion < 1)
851       {
852         char buf[MSG_SIZ];
853         int len;
854
855         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
856                        appData.secondProtocolVersion);
857         if( (len > MSG_SIZ) && appData.debugMode )
858           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
859
860         DisplayFatalError(buf, 0, 2);
861       }
862     else
863       {
864         second.protocolVersion = appData.secondProtocolVersion;
865       }
866
867     if (appData.icsActive) {
868         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
869 //    } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
870     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
871         appData.clockMode = FALSE;
872         first.sendTime = second.sendTime = 0;
873     }
874
875 #if ZIPPY
876     /* Override some settings from environment variables, for backward
877        compatibility.  Unfortunately it's not feasible to have the env
878        vars just set defaults, at least in xboard.  Ugh.
879     */
880     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
881       ZippyInit();
882     }
883 #endif
884
885     if (appData.noChessProgram) {
886         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
887         sprintf(programVersion, "%s", PACKAGE_STRING);
888     } else {
889       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
890       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
891       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
892     }
893
894     if (!appData.icsActive) {
895       char buf[MSG_SIZ];
896       int len;
897
898       /* Check for variants that are supported only in ICS mode,
899          or not at all.  Some that are accepted here nevertheless
900          have bugs; see comments below.
901       */
902       VariantClass variant = StringToVariant(appData.variant);
903       switch (variant) {
904       case VariantBughouse:     /* need four players and two boards */
905       case VariantKriegspiel:   /* need to hide pieces and move details */
906         /* case VariantFischeRandom: (Fabien: moved below) */
907         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
908         if( (len > MSG_SIZ) && appData.debugMode )
909           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
910
911         DisplayFatalError(buf, 0, 2);
912         return;
913
914       case VariantUnknown:
915       case VariantLoadable:
916       case Variant29:
917       case Variant30:
918       case Variant31:
919       case Variant32:
920       case Variant33:
921       case Variant34:
922       case Variant35:
923       case Variant36:
924       default:
925         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
926         if( (len > MSG_SIZ) && appData.debugMode )
927           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
928
929         DisplayFatalError(buf, 0, 2);
930         return;
931
932       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
933       case VariantFairy:      /* [HGM] TestLegality definitely off! */
934       case VariantGothic:     /* [HGM] should work */
935       case VariantCapablanca: /* [HGM] should work */
936       case VariantCourier:    /* [HGM] initial forced moves not implemented */
937       case VariantShogi:      /* [HGM] could still mate with pawn drop */
938       case VariantKnightmate: /* [HGM] should work */
939       case VariantCylinder:   /* [HGM] untested */
940       case VariantFalcon:     /* [HGM] untested */
941       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
942                                  offboard interposition not understood */
943       case VariantNormal:     /* definitely works! */
944       case VariantWildCastle: /* pieces not automatically shuffled */
945       case VariantNoCastle:   /* pieces not automatically shuffled */
946       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
947       case VariantLosers:     /* should work except for win condition,
948                                  and doesn't know captures are mandatory */
949       case VariantSuicide:    /* should work except for win condition,
950                                  and doesn't know captures are mandatory */
951       case VariantGiveaway:   /* should work except for win condition,
952                                  and doesn't know captures are mandatory */
953       case VariantTwoKings:   /* should work */
954       case VariantAtomic:     /* should work except for win condition */
955       case Variant3Check:     /* should work except for win condition */
956       case VariantShatranj:   /* should work except for all win conditions */
957       case VariantMakruk:     /* should work except for daw countdown */
958       case VariantBerolina:   /* might work if TestLegality is off */
959       case VariantCapaRandom: /* should work */
960       case VariantJanus:      /* should work */
961       case VariantSuper:      /* experimental */
962       case VariantGreat:      /* experimental, requires legality testing to be off */
963         break;
964       }
965     }
966
967     InitEngineUCI( installDir, &first );  // [HGM] moved here from winboard.c, to make available in xboard
968     InitEngineUCI( installDir, &second );
969 }
970
971 int NextIntegerFromString( char ** str, long * value )
972 {
973     int result = -1;
974     char * s = *str;
975
976     while( *s == ' ' || *s == '\t' ) {
977         s++;
978     }
979
980     *value = 0;
981
982     if( *s >= '0' && *s <= '9' ) {
983         while( *s >= '0' && *s <= '9' ) {
984             *value = *value * 10 + (*s - '0');
985             s++;
986         }
987
988         result = 0;
989     }
990
991     *str = s;
992
993     return result;
994 }
995
996 int NextTimeControlFromString( char ** str, long * value )
997 {
998     long temp;
999     int result = NextIntegerFromString( str, &temp );
1000
1001     if( result == 0 ) {
1002         *value = temp * 60; /* Minutes */
1003         if( **str == ':' ) {
1004             (*str)++;
1005             result = NextIntegerFromString( str, &temp );
1006             *value += temp; /* Seconds */
1007         }
1008     }
1009
1010     return result;
1011 }
1012
1013 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1014 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1015     int result = -1, type = 0; long temp, temp2;
1016
1017     if(**str != ':') return -1; // old params remain in force!
1018     (*str)++;
1019     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1020     if( NextIntegerFromString( str, &temp ) ) return -1;
1021     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1022
1023     if(**str != '/') {
1024         /* time only: incremental or sudden-death time control */
1025         if(**str == '+') { /* increment follows; read it */
1026             (*str)++;
1027             if(**str == '!') type = *(*str)++; // Bronstein TC
1028             if(result = NextIntegerFromString( str, &temp2)) return -1;
1029             *inc = temp2 * 1000;
1030             if(**str == '.') { // read fraction of increment
1031                 char *start = ++(*str);
1032                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1033                 temp2 *= 1000;
1034                 while(start++ < *str) temp2 /= 10;
1035                 *inc += temp2;
1036             }
1037         } else *inc = 0;
1038         *moves = 0; *tc = temp * 1000; *incType = type;
1039         return 0;
1040     }
1041
1042     (*str)++; /* classical time control */
1043     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1044
1045     if(result == 0) {
1046         *moves = temp;
1047         *tc    = temp2 * 1000;
1048         *inc   = 0;
1049         *incType = type;
1050     }
1051     return result;
1052 }
1053
1054 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1055 {   /* [HGM] get time to add from the multi-session time-control string */
1056     int incType, moves=1; /* kludge to force reading of first session */
1057     long time, increment;
1058     char *s = tcString;
1059
1060     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1061     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1062     do {
1063         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1064         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1065         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1066         if(movenr == -1) return time;    /* last move before new session     */
1067         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1068         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1069         if(!moves) return increment;     /* current session is incremental   */
1070         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1071     } while(movenr >= -1);               /* try again for next session       */
1072
1073     return 0; // no new time quota on this move
1074 }
1075
1076 int
1077 ParseTimeControl(tc, ti, mps)
1078      char *tc;
1079      float ti;
1080      int mps;
1081 {
1082   long tc1;
1083   long tc2;
1084   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1085   int min, sec=0;
1086   int len;
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           // str looks something like "Q/a1-a2"; kill the slash
4341           if(str[1] == '/')
4342             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4343           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4344           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4345                 strcat(buf, prom); // long move lacks promo specification!
4346           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4347                 if(appData.debugMode)
4348                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4349                 safeStrCpy(move_str, buf, sizeof(move_str)/sizeof(move_str[0]));
4350           }
4351           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4352                                 &fromX, &fromY, &toX, &toY, &promoChar)
4353                || ParseOneMove(buf, moveNum - 1, &moveType,
4354                                 &fromX, &fromY, &toX, &toY, &promoChar);
4355           // end of long SAN patch
4356           if (valid) {
4357             (void) CoordsToAlgebraic(boards[moveNum - 1],
4358                                      PosFlags(moveNum - 1),
4359                                      fromY, fromX, toY, toX, promoChar,
4360                                      parseList[moveNum-1]);
4361             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4362               case MT_NONE:
4363               case MT_STALEMATE:
4364               default:
4365                 break;
4366               case MT_CHECK:
4367                 if(gameInfo.variant != VariantShogi)
4368                     strcat(parseList[moveNum - 1], "+");
4369                 break;
4370               case MT_CHECKMATE:
4371               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4372                 strcat(parseList[moveNum - 1], "#");
4373                 break;
4374             }
4375             strcat(parseList[moveNum - 1], " ");
4376             strcat(parseList[moveNum - 1], elapsed_time);
4377             /* currentMoveString is set as a side-effect of ParseOneMove */
4378             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4379             strcat(moveList[moveNum - 1], "\n");
4380           } else {
4381             /* Move from ICS was illegal!?  Punt. */
4382             if (appData.debugMode) {
4383               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4384               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4385             }
4386             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4387             strcat(parseList[moveNum - 1], " ");
4388             strcat(parseList[moveNum - 1], elapsed_time);
4389             moveList[moveNum - 1][0] = NULLCHAR;
4390             fromX = fromY = toX = toY = -1;
4391           }
4392         }
4393   if (appData.debugMode) {
4394     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4395     setbuf(debugFP, NULL);
4396   }
4397
4398 #if ZIPPY
4399         /* Send move to chess program (BEFORE animating it). */
4400         if (appData.zippyPlay && !newGame && newMove &&
4401            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4402
4403             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4404                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4405                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4406                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4407                             move_str);
4408                     DisplayError(str, 0);
4409                 } else {
4410                     if (first.sendTime) {
4411                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4412                     }
4413                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4414                     if (firstMove && !bookHit) {
4415                         firstMove = FALSE;
4416                         if (first.useColors) {
4417                           SendToProgram(gameMode == IcsPlayingWhite ?
4418                                         "white\ngo\n" :
4419                                         "black\ngo\n", &first);
4420                         } else {
4421                           SendToProgram("go\n", &first);
4422                         }
4423                         first.maybeThinking = TRUE;
4424                     }
4425                 }
4426             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4427               if (moveList[moveNum - 1][0] == NULLCHAR) {
4428                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4429                 DisplayError(str, 0);
4430               } else {
4431                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4432                 SendMoveToProgram(moveNum - 1, &first);
4433               }
4434             }
4435         }
4436 #endif
4437     }
4438
4439     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4440         /* If move comes from a remote source, animate it.  If it
4441            isn't remote, it will have already been animated. */
4442         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4443             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4444         }
4445         if (!pausing && appData.highlightLastMove) {
4446             SetHighlights(fromX, fromY, toX, toY);
4447         }
4448     }
4449
4450     /* Start the clocks */
4451     whiteFlag = blackFlag = FALSE;
4452     appData.clockMode = !(basetime == 0 && increment == 0);
4453     if (ticking == 0) {
4454       ics_clock_paused = TRUE;
4455       StopClocks();
4456     } else if (ticking == 1) {
4457       ics_clock_paused = FALSE;
4458     }
4459     if (gameMode == IcsIdle ||
4460         relation == RELATION_OBSERVING_STATIC ||
4461         relation == RELATION_EXAMINING ||
4462         ics_clock_paused)
4463       DisplayBothClocks();
4464     else
4465       StartClocks();
4466
4467     /* Display opponents and material strengths */
4468     if (gameInfo.variant != VariantBughouse &&
4469         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4470         if (tinyLayout || smallLayout) {
4471             if(gameInfo.variant == VariantNormal)
4472               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4473                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4474                     basetime, increment);
4475             else
4476               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4477                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4478                     basetime, increment, (int) gameInfo.variant);
4479         } else {
4480             if(gameInfo.variant == VariantNormal)
4481               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4482                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4483                     basetime, increment);
4484             else
4485               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4486                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4487                     basetime, increment, VariantName(gameInfo.variant));
4488         }
4489         DisplayTitle(str);
4490   if (appData.debugMode) {
4491     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4492   }
4493     }
4494
4495
4496     /* Display the board */
4497     if (!pausing && !appData.noGUI) {
4498
4499       if (appData.premove)
4500           if (!gotPremove ||
4501              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4502              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4503               ClearPremoveHighlights();
4504
4505       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4506         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4507       DrawPosition(j, boards[currentMove]);
4508
4509       DisplayMove(moveNum - 1);
4510       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4511             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4512               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4513         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4514       }
4515     }
4516
4517     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4518 #if ZIPPY
4519     if(bookHit) { // [HGM] book: simulate book reply
4520         static char bookMove[MSG_SIZ]; // a bit generous?
4521
4522         programStats.nodes = programStats.depth = programStats.time =
4523         programStats.score = programStats.got_only_move = 0;
4524         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4525
4526         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4527         strcat(bookMove, bookHit);
4528         HandleMachineMove(bookMove, &first);
4529     }
4530 #endif
4531 }
4532
4533 void
4534 GetMoveListEvent()
4535 {
4536     char buf[MSG_SIZ];
4537     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4538         ics_getting_history = H_REQUESTED;
4539         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4540         SendToICS(buf);
4541     }
4542 }
4543
4544 void
4545 AnalysisPeriodicEvent(force)
4546      int force;
4547 {
4548     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4549          && !force) || !appData.periodicUpdates)
4550       return;
4551
4552     /* Send . command to Crafty to collect stats */
4553     SendToProgram(".\n", &first);
4554
4555     /* Don't send another until we get a response (this makes
4556        us stop sending to old Crafty's which don't understand
4557        the "." command (sending illegal cmds resets node count & time,
4558        which looks bad)) */
4559     programStats.ok_to_send = 0;
4560 }
4561
4562 void ics_update_width(new_width)
4563         int new_width;
4564 {
4565         ics_printf("set width %d\n", new_width);
4566 }
4567
4568 void
4569 SendMoveToProgram(moveNum, cps)
4570      int moveNum;
4571      ChessProgramState *cps;
4572 {
4573     char buf[MSG_SIZ];
4574
4575     if (cps->useUsermove) {
4576       SendToProgram("usermove ", cps);
4577     }
4578     if (cps->useSAN) {
4579       char *space;
4580       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4581         int len = space - parseList[moveNum];
4582         memcpy(buf, parseList[moveNum], len);
4583         buf[len++] = '\n';
4584         buf[len] = NULLCHAR;
4585       } else {
4586         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4587       }
4588       SendToProgram(buf, cps);
4589     } else {
4590       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4591         AlphaRank(moveList[moveNum], 4);
4592         SendToProgram(moveList[moveNum], cps);
4593         AlphaRank(moveList[moveNum], 4); // and back
4594       } else
4595       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4596        * the engine. It would be nice to have a better way to identify castle
4597        * moves here. */
4598       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4599                                                                          && cps->useOOCastle) {
4600         int fromX = moveList[moveNum][0] - AAA;
4601         int fromY = moveList[moveNum][1] - ONE;
4602         int toX = moveList[moveNum][2] - AAA;
4603         int toY = moveList[moveNum][3] - ONE;
4604         if((boards[moveNum][fromY][fromX] == WhiteKing
4605             && boards[moveNum][toY][toX] == WhiteRook)
4606            || (boards[moveNum][fromY][fromX] == BlackKing
4607                && boards[moveNum][toY][toX] == BlackRook)) {
4608           if(toX > fromX) SendToProgram("O-O\n", cps);
4609           else SendToProgram("O-O-O\n", cps);
4610         }
4611         else SendToProgram(moveList[moveNum], cps);
4612       }
4613       else SendToProgram(moveList[moveNum], cps);
4614       /* End of additions by Tord */
4615     }
4616
4617     /* [HGM] setting up the opening has brought engine in force mode! */
4618     /*       Send 'go' if we are in a mode where machine should play. */
4619     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4620         (gameMode == TwoMachinesPlay   ||
4621 #if ZIPPY
4622          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4623 #endif
4624          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4625         SendToProgram("go\n", cps);
4626   if (appData.debugMode) {
4627     fprintf(debugFP, "(extra)\n");
4628   }
4629     }
4630     setboardSpoiledMachineBlack = 0;
4631 }
4632
4633 void
4634 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4635      ChessMove moveType;
4636      int fromX, fromY, toX, toY;
4637      char promoChar;
4638 {
4639     char user_move[MSG_SIZ];
4640
4641     switch (moveType) {
4642       default:
4643         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4644                 (int)moveType, fromX, fromY, toX, toY);
4645         DisplayError(user_move + strlen("say "), 0);
4646         break;
4647       case WhiteKingSideCastle:
4648       case BlackKingSideCastle:
4649       case WhiteQueenSideCastleWild:
4650       case BlackQueenSideCastleWild:
4651       /* PUSH Fabien */
4652       case WhiteHSideCastleFR:
4653       case BlackHSideCastleFR:
4654       /* POP Fabien */
4655         snprintf(user_move, MSG_SIZ, "o-o\n");
4656         break;
4657       case WhiteQueenSideCastle:
4658       case BlackQueenSideCastle:
4659       case WhiteKingSideCastleWild:
4660       case BlackKingSideCastleWild:
4661       /* PUSH Fabien */
4662       case WhiteASideCastleFR:
4663       case BlackASideCastleFR:
4664       /* POP Fabien */
4665         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4666         break;
4667       case WhiteNonPromotion:
4668       case BlackNonPromotion:
4669         sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4670         break;
4671       case WhitePromotion:
4672       case BlackPromotion:
4673         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4674           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4675                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4676                 PieceToChar(WhiteFerz));
4677         else if(gameInfo.variant == VariantGreat)
4678           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4679                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4680                 PieceToChar(WhiteMan));
4681         else
4682           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4683                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4684                 promoChar);
4685         break;
4686       case WhiteDrop:
4687       case BlackDrop:
4688         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4689                 ToUpper(PieceToChar((ChessSquare) fromX)),
4690                 AAA + toX, ONE + toY);
4691         break;
4692       case NormalMove:
4693       case WhiteCapturesEnPassant:
4694       case BlackCapturesEnPassant:
4695       case IllegalMove:  /* could be a variant we don't quite understand */
4696         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4697                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4698         break;
4699     }
4700     SendToICS(user_move);
4701     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4702         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4703 }
4704
4705 void
4706 UploadGameEvent()
4707 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4708     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4709     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4710     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4711         DisplayError("You cannot do this while you are playing or observing", 0);
4712         return;
4713     }
4714     if(gameMode != IcsExamining) { // is this ever not the case?
4715         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4716
4717         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4718           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4719         } else { // on FICS we must first go to general examine mode
4720           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4721         }
4722         if(gameInfo.variant != VariantNormal) {
4723             // try figure out wild number, as xboard names are not always valid on ICS
4724             for(i=1; i<=36; i++) {
4725               snprintf(buf, MSG_SIZ, "wild/%d", i);
4726                 if(StringToVariant(buf) == gameInfo.variant) break;
4727             }
4728             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4729             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4730             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4731         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4732         SendToICS(ics_prefix);
4733         SendToICS(buf);
4734         if(startedFromSetupPosition || backwardMostMove != 0) {
4735           fen = PositionToFEN(backwardMostMove, NULL);
4736           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4737             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4738             SendToICS(buf);
4739           } else { // FICS: everything has to set by separate bsetup commands
4740             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4741             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4742             SendToICS(buf);
4743             if(!WhiteOnMove(backwardMostMove)) {
4744                 SendToICS("bsetup tomove black\n");
4745             }
4746             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4747             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4748             SendToICS(buf);
4749             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4750             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4751             SendToICS(buf);
4752             i = boards[backwardMostMove][EP_STATUS];
4753             if(i >= 0) { // set e.p.
4754               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4755                 SendToICS(buf);
4756             }
4757             bsetup++;
4758           }
4759         }
4760       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4761             SendToICS("bsetup done\n"); // switch to normal examining.
4762     }
4763     for(i = backwardMostMove; i<last; i++) {
4764         char buf[20];
4765         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4766         SendToICS(buf);
4767     }
4768     SendToICS(ics_prefix);
4769     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4770 }
4771
4772 void
4773 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4774      int rf, ff, rt, ft;
4775      char promoChar;
4776      char move[7];
4777 {
4778     if (rf == DROP_RANK) {
4779       sprintf(move, "%c@%c%c\n",
4780                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4781     } else {
4782         if (promoChar == 'x' || promoChar == NULLCHAR) {
4783           sprintf(move, "%c%c%c%c\n",
4784                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4785         } else {
4786             sprintf(move, "%c%c%c%c%c\n",
4787                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4788         }
4789     }
4790 }
4791
4792 void
4793 ProcessICSInitScript(f)
4794      FILE *f;
4795 {
4796     char buf[MSG_SIZ];
4797
4798     while (fgets(buf, MSG_SIZ, f)) {
4799         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4800     }
4801
4802     fclose(f);
4803 }
4804
4805
4806 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4807 void
4808 AlphaRank(char *move, int n)
4809 {
4810 //    char *p = move, c; int x, y;
4811
4812     if (appData.debugMode) {
4813         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4814     }
4815
4816     if(move[1]=='*' &&
4817        move[2]>='0' && move[2]<='9' &&
4818        move[3]>='a' && move[3]<='x'    ) {
4819         move[1] = '@';
4820         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4821         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4822     } else
4823     if(move[0]>='0' && move[0]<='9' &&
4824        move[1]>='a' && move[1]<='x' &&
4825        move[2]>='0' && move[2]<='9' &&
4826        move[3]>='a' && move[3]<='x'    ) {
4827         /* input move, Shogi -> normal */
4828         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4829         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4830         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4831         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4832     } else
4833     if(move[1]=='@' &&
4834        move[3]>='0' && move[3]<='9' &&
4835        move[2]>='a' && move[2]<='x'    ) {
4836         move[1] = '*';
4837         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4838         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4839     } else
4840     if(
4841        move[0]>='a' && move[0]<='x' &&
4842        move[3]>='0' && move[3]<='9' &&
4843        move[2]>='a' && move[2]<='x'    ) {
4844          /* output move, normal -> Shogi */
4845         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4846         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4847         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4848         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4849         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4850     }
4851     if (appData.debugMode) {
4852         fprintf(debugFP, "   out = '%s'\n", move);
4853     }
4854 }
4855
4856 char yy_textstr[8000];
4857
4858 /* Parser for moves from gnuchess, ICS, or user typein box */
4859 Boolean
4860 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
4861      char *move;
4862      int moveNum;
4863      ChessMove *moveType;
4864      int *fromX, *fromY, *toX, *toY;
4865      char *promoChar;
4866 {
4867     if (appData.debugMode) {
4868         fprintf(debugFP, "move to parse: %s\n", move);
4869     }
4870     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
4871
4872     switch (*moveType) {
4873       case WhitePromotion:
4874       case BlackPromotion:
4875       case WhiteNonPromotion:
4876       case BlackNonPromotion:
4877       case NormalMove:
4878       case WhiteCapturesEnPassant:
4879       case BlackCapturesEnPassant:
4880       case WhiteKingSideCastle:
4881       case WhiteQueenSideCastle:
4882       case BlackKingSideCastle:
4883       case BlackQueenSideCastle:
4884       case WhiteKingSideCastleWild:
4885       case WhiteQueenSideCastleWild:
4886       case BlackKingSideCastleWild:
4887       case BlackQueenSideCastleWild:
4888       /* Code added by Tord: */
4889       case WhiteHSideCastleFR:
4890       case WhiteASideCastleFR:
4891       case BlackHSideCastleFR:
4892       case BlackASideCastleFR:
4893       /* End of code added by Tord */
4894       case IllegalMove:         /* bug or odd chess variant */
4895         *fromX = currentMoveString[0] - AAA;
4896         *fromY = currentMoveString[1] - ONE;
4897         *toX = currentMoveString[2] - AAA;
4898         *toY = currentMoveString[3] - ONE;
4899         *promoChar = currentMoveString[4];
4900         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
4901             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
4902     if (appData.debugMode) {
4903         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
4904     }
4905             *fromX = *fromY = *toX = *toY = 0;
4906             return FALSE;
4907         }
4908         if (appData.testLegality) {
4909           return (*moveType != IllegalMove);
4910         } else {
4911           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
4912                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
4913         }
4914
4915       case WhiteDrop:
4916       case BlackDrop:
4917         *fromX = *moveType == WhiteDrop ?
4918           (int) CharToPiece(ToUpper(currentMoveString[0])) :
4919           (int) CharToPiece(ToLower(currentMoveString[0]));
4920         *fromY = DROP_RANK;
4921         *toX = currentMoveString[2] - AAA;
4922         *toY = currentMoveString[3] - ONE;
4923         *promoChar = NULLCHAR;
4924         return TRUE;
4925
4926       case AmbiguousMove:
4927       case ImpossibleMove:
4928       case EndOfFile:
4929       case ElapsedTime:
4930       case Comment:
4931       case PGNTag:
4932       case NAG:
4933       case WhiteWins:
4934       case BlackWins:
4935       case GameIsDrawn:
4936       default:
4937     if (appData.debugMode) {
4938         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
4939     }
4940         /* bug? */
4941         *fromX = *fromY = *toX = *toY = 0;
4942         *promoChar = NULLCHAR;
4943         return FALSE;
4944     }
4945 }
4946
4947
4948 void
4949 ParsePV(char *pv, Boolean storeComments)
4950 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
4951   int fromX, fromY, toX, toY; char promoChar;
4952   ChessMove moveType;
4953   Boolean valid;
4954   int nr = 0;
4955
4956   endPV = forwardMostMove;
4957   do {
4958     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
4959     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
4960     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
4961 if(appData.debugMode){
4962 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);
4963 }
4964     if(!valid && nr == 0 &&
4965        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
4966         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
4967         // Hande case where played move is different from leading PV move
4968         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
4969         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
4970         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
4971         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
4972           endPV += 2; // if position different, keep this
4973           moveList[endPV-1][0] = fromX + AAA;
4974           moveList[endPV-1][1] = fromY + ONE;
4975           moveList[endPV-1][2] = toX + AAA;
4976           moveList[endPV-1][3] = toY + ONE;
4977           parseList[endPV-1][0] = NULLCHAR;
4978           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
4979         }
4980       }
4981     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
4982     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
4983     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
4984     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
4985         valid++; // allow comments in PV
4986         continue;
4987     }
4988     nr++;
4989     if(endPV+1 > framePtr) break; // no space, truncate
4990     if(!valid) break;
4991     endPV++;
4992     CopyBoard(boards[endPV], boards[endPV-1]);
4993     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
4994     moveList[endPV-1][0] = fromX + AAA;
4995     moveList[endPV-1][1] = fromY + ONE;
4996     moveList[endPV-1][2] = toX + AAA;
4997     moveList[endPV-1][3] = toY + ONE;
4998     if(storeComments)
4999         CoordsToAlgebraic(boards[endPV - 1],
5000                              PosFlags(endPV - 1),
5001                              fromY, fromX, toY, toX, promoChar,
5002                              parseList[endPV - 1]);
5003     else
5004         parseList[endPV-1][0] = NULLCHAR;
5005   } while(valid);
5006   currentMove = endPV;
5007   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5008   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5009                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5010   DrawPosition(TRUE, boards[currentMove]);
5011 }
5012
5013 static int lastX, lastY;
5014
5015 Boolean
5016 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5017 {
5018         int startPV;
5019         char *p;
5020
5021         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5022         lastX = x; lastY = y;
5023         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5024         startPV = index;
5025         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5026         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5027         index = startPV;
5028         do{ while(buf[index] && buf[index] != '\n') index++;
5029         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5030         buf[index] = 0;
5031         ParsePV(buf+startPV, FALSE);
5032         *start = startPV; *end = index-1;
5033         return TRUE;
5034 }
5035
5036 Boolean
5037 LoadPV(int x, int y)
5038 { // called on right mouse click to load PV
5039   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5040   lastX = x; lastY = y;
5041   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5042   return TRUE;
5043 }
5044
5045 void
5046 UnLoadPV()
5047 {
5048   if(endPV < 0) return;
5049   endPV = -1;
5050   currentMove = forwardMostMove;
5051   ClearPremoveHighlights();
5052   DrawPosition(TRUE, boards[currentMove]);
5053 }
5054
5055 void
5056 MovePV(int x, int y, int h)
5057 { // step through PV based on mouse coordinates (called on mouse move)
5058   int margin = h>>3, step = 0;
5059
5060   if(endPV < 0) return;
5061   // we must somehow check if right button is still down (might be released off board!)
5062   if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
5063   if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
5064   if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
5065   if(!step) return;
5066   lastX = x; lastY = y;
5067   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5068   currentMove += step;
5069   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5070   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5071                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5072   DrawPosition(FALSE, boards[currentMove]);
5073 }
5074
5075
5076 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5077 // All positions will have equal probability, but the current method will not provide a unique
5078 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5079 #define DARK 1
5080 #define LITE 2
5081 #define ANY 3
5082
5083 int squaresLeft[4];
5084 int piecesLeft[(int)BlackPawn];
5085 int seed, nrOfShuffles;
5086
5087 void GetPositionNumber()
5088 {       // sets global variable seed
5089         int i;
5090
5091         seed = appData.defaultFrcPosition;
5092         if(seed < 0) { // randomize based on time for negative FRC position numbers
5093                 for(i=0; i<50; i++) seed += random();
5094                 seed = random() ^ random() >> 8 ^ random() << 8;
5095                 if(seed<0) seed = -seed;
5096         }
5097 }
5098
5099 int put(Board board, int pieceType, int rank, int n, int shade)
5100 // put the piece on the (n-1)-th empty squares of the given shade
5101 {
5102         int i;
5103
5104         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5105                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5106                         board[rank][i] = (ChessSquare) pieceType;
5107                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5108                         squaresLeft[ANY]--;
5109                         piecesLeft[pieceType]--;
5110                         return i;
5111                 }
5112         }
5113         return -1;
5114 }
5115
5116
5117 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5118 // calculate where the next piece goes, (any empty square), and put it there
5119 {
5120         int i;
5121
5122         i = seed % squaresLeft[shade];
5123         nrOfShuffles *= squaresLeft[shade];
5124         seed /= squaresLeft[shade];
5125         put(board, pieceType, rank, i, shade);
5126 }
5127
5128 void AddTwoPieces(Board board, int pieceType, int rank)
5129 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5130 {
5131         int i, n=squaresLeft[ANY], j=n-1, k;
5132
5133         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5134         i = seed % k;  // pick one
5135         nrOfShuffles *= k;
5136         seed /= k;
5137         while(i >= j) i -= j--;
5138         j = n - 1 - j; i += j;
5139         put(board, pieceType, rank, j, ANY);
5140         put(board, pieceType, rank, i, ANY);
5141 }
5142
5143 void SetUpShuffle(Board board, int number)
5144 {
5145         int i, p, first=1;
5146
5147         GetPositionNumber(); nrOfShuffles = 1;
5148
5149         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5150         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5151         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5152
5153         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5154
5155         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5156             p = (int) board[0][i];
5157             if(p < (int) BlackPawn) piecesLeft[p] ++;
5158             board[0][i] = EmptySquare;
5159         }
5160
5161         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5162             // shuffles restricted to allow normal castling put KRR first
5163             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5164                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5165             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5166                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5167             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5168                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5169             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5170                 put(board, WhiteRook, 0, 0, ANY);
5171             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5172         }
5173
5174         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5175             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5176             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5177                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5178                 while(piecesLeft[p] >= 2) {
5179                     AddOnePiece(board, p, 0, LITE);
5180                     AddOnePiece(board, p, 0, DARK);
5181                 }
5182                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5183             }
5184
5185         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5186             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5187             // but we leave King and Rooks for last, to possibly obey FRC restriction
5188             if(p == (int)WhiteRook) continue;
5189             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5190             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5191         }
5192
5193         // now everything is placed, except perhaps King (Unicorn) and Rooks
5194
5195         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5196             // Last King gets castling rights
5197             while(piecesLeft[(int)WhiteUnicorn]) {
5198                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5199                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5200             }
5201
5202             while(piecesLeft[(int)WhiteKing]) {
5203                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5204                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5205             }
5206
5207
5208         } else {
5209             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5210             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5211         }
5212
5213         // Only Rooks can be left; simply place them all
5214         while(piecesLeft[(int)WhiteRook]) {
5215                 i = put(board, WhiteRook, 0, 0, ANY);
5216                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5217                         if(first) {
5218                                 first=0;
5219                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5220                         }
5221                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5222                 }
5223         }
5224         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5225             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5226         }
5227
5228         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5229 }
5230
5231 int SetCharTable( char *table, const char * map )
5232 /* [HGM] moved here from winboard.c because of its general usefulness */
5233 /*       Basically a safe strcpy that uses the last character as King */
5234 {
5235     int result = FALSE; int NrPieces;
5236
5237     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5238                     && NrPieces >= 12 && !(NrPieces&1)) {
5239         int i; /* [HGM] Accept even length from 12 to 34 */
5240
5241         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5242         for( i=0; i<NrPieces/2-1; i++ ) {
5243             table[i] = map[i];
5244             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5245         }
5246         table[(int) WhiteKing]  = map[NrPieces/2-1];
5247         table[(int) BlackKing]  = map[NrPieces-1];
5248
5249         result = TRUE;
5250     }
5251
5252     return result;
5253 }
5254
5255 void Prelude(Board board)
5256 {       // [HGM] superchess: random selection of exo-pieces
5257         int i, j, k; ChessSquare p;
5258         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5259
5260         GetPositionNumber(); // use FRC position number
5261
5262         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5263             SetCharTable(pieceToChar, appData.pieceToCharTable);
5264             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5265                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5266         }
5267
5268         j = seed%4;                 seed /= 4;
5269         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5270         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5271         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5272         j = seed%3 + (seed%3 >= j); seed /= 3;
5273         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5274         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5275         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5276         j = seed%3;                 seed /= 3;
5277         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5278         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5279         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5280         j = seed%2 + (seed%2 >= j); seed /= 2;
5281         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5282         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5283         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5284         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5285         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5286         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5287         put(board, exoPieces[0],    0, 0, ANY);
5288         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5289 }
5290
5291 void
5292 InitPosition(redraw)
5293      int redraw;
5294 {
5295     ChessSquare (* pieces)[BOARD_FILES];
5296     int i, j, pawnRow, overrule,
5297     oldx = gameInfo.boardWidth,
5298     oldy = gameInfo.boardHeight,
5299     oldh = gameInfo.holdingsWidth,
5300     oldv = gameInfo.variant;
5301
5302     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5303
5304     /* [AS] Initialize pv info list [HGM] and game status */
5305     {
5306         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5307             pvInfoList[i].depth = 0;
5308             boards[i][EP_STATUS] = EP_NONE;
5309             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5310         }
5311
5312         initialRulePlies = 0; /* 50-move counter start */
5313
5314         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5315         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5316     }
5317
5318
5319     /* [HGM] logic here is completely changed. In stead of full positions */
5320     /* the initialized data only consist of the two backranks. The switch */
5321     /* selects which one we will use, which is than copied to the Board   */
5322     /* initialPosition, which for the rest is initialized by Pawns and    */
5323     /* empty squares. This initial position is then copied to boards[0],  */
5324     /* possibly after shuffling, so that it remains available.            */
5325
5326     gameInfo.holdingsWidth = 0; /* default board sizes */
5327     gameInfo.boardWidth    = 8;
5328     gameInfo.boardHeight   = 8;
5329     gameInfo.holdingsSize  = 0;
5330     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5331     for(i=0; i<BOARD_FILES-2; i++)
5332       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5333     initialPosition[EP_STATUS] = EP_NONE;
5334     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5335     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5336          SetCharTable(pieceNickName, appData.pieceNickNames);
5337     else SetCharTable(pieceNickName, "............");
5338
5339     switch (gameInfo.variant) {
5340     case VariantFischeRandom:
5341       shuffleOpenings = TRUE;
5342     default:
5343       pieces = FIDEArray;
5344       break;
5345     case VariantShatranj:
5346       pieces = ShatranjArray;
5347       nrCastlingRights = 0;
5348       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5349       break;
5350     case VariantMakruk:
5351       pieces = makrukArray;
5352       nrCastlingRights = 0;
5353       startedFromSetupPosition = TRUE;
5354       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5355       break;
5356     case VariantTwoKings:
5357       pieces = twoKingsArray;
5358       break;
5359     case VariantCapaRandom:
5360       shuffleOpenings = TRUE;
5361     case VariantCapablanca:
5362       pieces = CapablancaArray;
5363       gameInfo.boardWidth = 10;
5364       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5365       break;
5366     case VariantGothic:
5367       pieces = GothicArray;
5368       gameInfo.boardWidth = 10;
5369       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5370       break;
5371     case VariantJanus:
5372       pieces = JanusArray;
5373       gameInfo.boardWidth = 10;
5374       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5375       nrCastlingRights = 6;
5376         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5377         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5378         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5379         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5380         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5381         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5382       break;
5383     case VariantFalcon:
5384       pieces = FalconArray;
5385       gameInfo.boardWidth = 10;
5386       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5387       break;
5388     case VariantXiangqi:
5389       pieces = XiangqiArray;
5390       gameInfo.boardWidth  = 9;
5391       gameInfo.boardHeight = 10;
5392       nrCastlingRights = 0;
5393       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5394       break;
5395     case VariantShogi:
5396       pieces = ShogiArray;
5397       gameInfo.boardWidth  = 9;
5398       gameInfo.boardHeight = 9;
5399       gameInfo.holdingsSize = 7;
5400       nrCastlingRights = 0;
5401       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5402       break;
5403     case VariantCourier:
5404       pieces = CourierArray;
5405       gameInfo.boardWidth  = 12;
5406       nrCastlingRights = 0;
5407       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5408       break;
5409     case VariantKnightmate:
5410       pieces = KnightmateArray;
5411       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5412       break;
5413     case VariantFairy:
5414       pieces = fairyArray;
5415       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5416       break;
5417     case VariantGreat:
5418       pieces = GreatArray;
5419       gameInfo.boardWidth = 10;
5420       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5421       gameInfo.holdingsSize = 8;
5422       break;
5423     case VariantSuper:
5424       pieces = FIDEArray;
5425       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5426       gameInfo.holdingsSize = 8;
5427       startedFromSetupPosition = TRUE;
5428       break;
5429     case VariantCrazyhouse:
5430     case VariantBughouse:
5431       pieces = FIDEArray;
5432       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5433       gameInfo.holdingsSize = 5;
5434       break;
5435     case VariantWildCastle:
5436       pieces = FIDEArray;
5437       /* !!?shuffle with kings guaranteed to be on d or e file */
5438       shuffleOpenings = 1;
5439       break;
5440     case VariantNoCastle:
5441       pieces = FIDEArray;
5442       nrCastlingRights = 0;
5443       /* !!?unconstrained back-rank shuffle */
5444       shuffleOpenings = 1;
5445       break;
5446     }
5447
5448     overrule = 0;
5449     if(appData.NrFiles >= 0) {
5450         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5451         gameInfo.boardWidth = appData.NrFiles;
5452     }
5453     if(appData.NrRanks >= 0) {
5454         gameInfo.boardHeight = appData.NrRanks;
5455     }
5456     if(appData.holdingsSize >= 0) {
5457         i = appData.holdingsSize;
5458         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5459         gameInfo.holdingsSize = i;
5460     }
5461     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5462     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5463         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5464
5465     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5466     if(pawnRow < 1) pawnRow = 1;
5467     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5468
5469     /* User pieceToChar list overrules defaults */
5470     if(appData.pieceToCharTable != NULL)
5471         SetCharTable(pieceToChar, appData.pieceToCharTable);
5472
5473     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5474
5475         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5476             s = (ChessSquare) 0; /* account holding counts in guard band */
5477         for( i=0; i<BOARD_HEIGHT; i++ )
5478             initialPosition[i][j] = s;
5479
5480         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5481         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5482         initialPosition[pawnRow][j] = WhitePawn;
5483         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
5484         if(gameInfo.variant == VariantXiangqi) {
5485             if(j&1) {
5486                 initialPosition[pawnRow][j] =
5487                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5488                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5489                    initialPosition[2][j] = WhiteCannon;
5490                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5491                 }
5492             }
5493         }
5494         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5495     }
5496     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5497
5498             j=BOARD_LEFT+1;
5499             initialPosition[1][j] = WhiteBishop;
5500             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5501             j=BOARD_RGHT-2;
5502             initialPosition[1][j] = WhiteRook;
5503             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5504     }
5505
5506     if( nrCastlingRights == -1) {
5507         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5508         /*       This sets default castling rights from none to normal corners   */
5509         /* Variants with other castling rights must set them themselves above    */
5510         nrCastlingRights = 6;
5511
5512         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5513         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5514         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5515         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5516         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5517         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5518      }
5519
5520      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5521      if(gameInfo.variant == VariantGreat) { // promotion commoners
5522         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5523         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5524         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5525         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5526      }
5527   if (appData.debugMode) {
5528     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5529   }
5530     if(shuffleOpenings) {
5531         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5532         startedFromSetupPosition = TRUE;
5533     }
5534     if(startedFromPositionFile) {
5535       /* [HGM] loadPos: use PositionFile for every new game */
5536       CopyBoard(initialPosition, filePosition);
5537       for(i=0; i<nrCastlingRights; i++)
5538           initialRights[i] = filePosition[CASTLING][i];
5539       startedFromSetupPosition = TRUE;
5540     }
5541
5542     CopyBoard(boards[0], initialPosition);
5543
5544     if(oldx != gameInfo.boardWidth ||
5545        oldy != gameInfo.boardHeight ||
5546        oldh != gameInfo.holdingsWidth
5547 #ifdef GOTHIC
5548        || oldv == VariantGothic ||        // For licensing popups
5549        gameInfo.variant == VariantGothic
5550 #endif
5551 #ifdef FALCON
5552        || oldv == VariantFalcon ||
5553        gameInfo.variant == VariantFalcon
5554 #endif
5555                                          )
5556             InitDrawingSizes(-2 ,0);
5557
5558     if (redraw)
5559       DrawPosition(TRUE, boards[currentMove]);
5560 }
5561
5562 void
5563 SendBoard(cps, moveNum)
5564      ChessProgramState *cps;
5565      int moveNum;
5566 {
5567     char message[MSG_SIZ];
5568
5569     if (cps->useSetboard) {
5570       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5571       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5572       SendToProgram(message, cps);
5573       free(fen);
5574
5575     } else {
5576       ChessSquare *bp;
5577       int i, j;
5578       /* Kludge to set black to move, avoiding the troublesome and now
5579        * deprecated "black" command.
5580        */
5581       if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
5582
5583       SendToProgram("edit\n", cps);
5584       SendToProgram("#\n", cps);
5585       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5586         bp = &boards[moveNum][i][BOARD_LEFT];
5587         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5588           if ((int) *bp < (int) BlackPawn) {
5589             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5590                     AAA + j, ONE + i);
5591             if(message[0] == '+' || message[0] == '~') {
5592               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5593                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5594                         AAA + j, ONE + i);
5595             }
5596             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5597                 message[1] = BOARD_RGHT   - 1 - j + '1';
5598                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5599             }
5600             SendToProgram(message, cps);
5601           }
5602         }
5603       }
5604
5605       SendToProgram("c\n", cps);
5606       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5607         bp = &boards[moveNum][i][BOARD_LEFT];
5608         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5609           if (((int) *bp != (int) EmptySquare)
5610               && ((int) *bp >= (int) BlackPawn)) {
5611             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5612                     AAA + j, ONE + i);
5613             if(message[0] == '+' || message[0] == '~') {
5614               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5615                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5616                         AAA + j, ONE + i);
5617             }
5618             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5619                 message[1] = BOARD_RGHT   - 1 - j + '1';
5620                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5621             }
5622             SendToProgram(message, cps);
5623           }
5624         }
5625       }
5626
5627       SendToProgram(".\n", cps);
5628     }
5629     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5630 }
5631
5632 static int autoQueen; // [HGM] oneclick
5633
5634 int
5635 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5636 {
5637     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5638     /* [HGM] add Shogi promotions */
5639     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5640     ChessSquare piece;
5641     ChessMove moveType;
5642     Boolean premove;
5643
5644     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5645     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5646
5647     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5648       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5649         return FALSE;
5650
5651     piece = boards[currentMove][fromY][fromX];
5652     if(gameInfo.variant == VariantShogi) {
5653         promotionZoneSize = BOARD_HEIGHT/3;
5654         highestPromotingPiece = (int)WhiteFerz;
5655     } else if(gameInfo.variant == VariantMakruk) {
5656         promotionZoneSize = 3;
5657     }
5658
5659     // next weed out all moves that do not touch the promotion zone at all
5660     if((int)piece >= BlackPawn) {
5661         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5662              return FALSE;
5663         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5664     } else {
5665         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5666            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5667     }
5668
5669     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5670
5671     // weed out mandatory Shogi promotions
5672     if(gameInfo.variant == VariantShogi) {
5673         if(piece >= BlackPawn) {
5674             if(toY == 0 && piece == BlackPawn ||
5675                toY == 0 && piece == BlackQueen ||
5676                toY <= 1 && piece == BlackKnight) {
5677                 *promoChoice = '+';
5678                 return FALSE;
5679             }
5680         } else {
5681             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5682                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5683                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5684                 *promoChoice = '+';
5685                 return FALSE;
5686             }
5687         }
5688     }
5689
5690     // weed out obviously illegal Pawn moves
5691     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5692         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5693         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5694         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5695         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5696         // note we are not allowed to test for valid (non-)capture, due to premove
5697     }
5698
5699     // we either have a choice what to promote to, or (in Shogi) whether to promote
5700     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5701         *promoChoice = PieceToChar(BlackFerz);  // no choice
5702         return FALSE;
5703     }
5704     if(autoQueen) { // predetermined
5705         if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
5706              *promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
5707         else *promoChoice = PieceToChar(BlackQueen);
5708         return FALSE;
5709     }
5710
5711     // suppress promotion popup on illegal moves that are not premoves
5712     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5713               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5714     if(appData.testLegality && !premove) {
5715         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5716                         fromY, fromX, toY, toX, NULLCHAR);
5717         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5718             return FALSE;
5719     }
5720
5721     return TRUE;
5722 }
5723
5724 int
5725 InPalace(row, column)
5726      int row, column;
5727 {   /* [HGM] for Xiangqi */
5728     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5729          column < (BOARD_WIDTH + 4)/2 &&
5730          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5731     return FALSE;
5732 }
5733
5734 int
5735 PieceForSquare (x, y)
5736      int x;
5737      int y;
5738 {
5739   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5740      return -1;
5741   else
5742      return boards[currentMove][y][x];
5743 }
5744
5745 int
5746 OKToStartUserMove(x, y)
5747      int x, y;
5748 {
5749     ChessSquare from_piece;
5750     int white_piece;
5751
5752     if (matchMode) return FALSE;
5753     if (gameMode == EditPosition) return TRUE;
5754
5755     if (x >= 0 && y >= 0)
5756       from_piece = boards[currentMove][y][x];
5757     else
5758       from_piece = EmptySquare;
5759
5760     if (from_piece == EmptySquare) return FALSE;
5761
5762     white_piece = (int)from_piece >= (int)WhitePawn &&
5763       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5764
5765     switch (gameMode) {
5766       case PlayFromGameFile:
5767       case AnalyzeFile:
5768       case TwoMachinesPlay:
5769       case EndOfGame:
5770         return FALSE;
5771
5772       case IcsObserving:
5773       case IcsIdle:
5774         return FALSE;
5775
5776       case MachinePlaysWhite:
5777       case IcsPlayingBlack:
5778         if (appData.zippyPlay) return FALSE;
5779         if (white_piece) {
5780             DisplayMoveError(_("You are playing Black"));
5781             return FALSE;
5782         }
5783         break;
5784
5785       case MachinePlaysBlack:
5786       case IcsPlayingWhite:
5787         if (appData.zippyPlay) return FALSE;
5788         if (!white_piece) {
5789             DisplayMoveError(_("You are playing White"));
5790             return FALSE;
5791         }
5792         break;
5793
5794       case EditGame:
5795         if (!white_piece && WhiteOnMove(currentMove)) {
5796             DisplayMoveError(_("It is White's turn"));
5797             return FALSE;
5798         }
5799         if (white_piece && !WhiteOnMove(currentMove)) {
5800             DisplayMoveError(_("It is Black's turn"));
5801             return FALSE;
5802         }
5803         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5804             /* Editing correspondence game history */
5805             /* Could disallow this or prompt for confirmation */
5806             cmailOldMove = -1;
5807         }
5808         break;
5809
5810       case BeginningOfGame:
5811         if (appData.icsActive) return FALSE;
5812         if (!appData.noChessProgram) {
5813             if (!white_piece) {
5814                 DisplayMoveError(_("You are playing White"));
5815                 return FALSE;
5816             }
5817         }
5818         break;
5819
5820       case Training:
5821         if (!white_piece && WhiteOnMove(currentMove)) {
5822             DisplayMoveError(_("It is White's turn"));
5823             return FALSE;
5824         }
5825         if (white_piece && !WhiteOnMove(currentMove)) {
5826             DisplayMoveError(_("It is Black's turn"));
5827             return FALSE;
5828         }
5829         break;
5830
5831       default:
5832       case IcsExamining:
5833         break;
5834     }
5835     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
5836         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
5837         && gameMode != AnalyzeFile && gameMode != Training) {
5838         DisplayMoveError(_("Displayed position is not current"));
5839         return FALSE;
5840     }
5841     return TRUE;
5842 }
5843
5844 Boolean
5845 OnlyMove(int *x, int *y, Boolean captures) {
5846     DisambiguateClosure cl;
5847     if (appData.zippyPlay) return FALSE;
5848     switch(gameMode) {
5849       case MachinePlaysBlack:
5850       case IcsPlayingWhite:
5851       case BeginningOfGame:
5852         if(!WhiteOnMove(currentMove)) return FALSE;
5853         break;
5854       case MachinePlaysWhite:
5855       case IcsPlayingBlack:
5856         if(WhiteOnMove(currentMove)) return FALSE;
5857         break;
5858       default:
5859         return FALSE;
5860     }
5861     cl.pieceIn = EmptySquare;
5862     cl.rfIn = *y;
5863     cl.ffIn = *x;
5864     cl.rtIn = -1;
5865     cl.ftIn = -1;
5866     cl.promoCharIn = NULLCHAR;
5867     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5868     if( cl.kind == NormalMove ||
5869         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5870         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5871         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5872       fromX = cl.ff;
5873       fromY = cl.rf;
5874       *x = cl.ft;
5875       *y = cl.rt;
5876       return TRUE;
5877     }
5878     if(cl.kind != ImpossibleMove) return FALSE;
5879     cl.pieceIn = EmptySquare;
5880     cl.rfIn = -1;
5881     cl.ffIn = -1;
5882     cl.rtIn = *y;
5883     cl.ftIn = *x;
5884     cl.promoCharIn = NULLCHAR;
5885     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
5886     if( cl.kind == NormalMove ||
5887         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
5888         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
5889         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
5890       fromX = cl.ff;
5891       fromY = cl.rf;
5892       *x = cl.ft;
5893       *y = cl.rt;
5894       autoQueen = TRUE; // act as if autoQueen on when we click to-square
5895       return TRUE;
5896     }
5897     return FALSE;
5898 }
5899
5900 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
5901 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
5902 int lastLoadGameUseList = FALSE;
5903 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
5904 ChessMove lastLoadGameStart = EndOfFile;
5905
5906 void
5907 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
5908      int fromX, fromY, toX, toY;
5909      int promoChar;
5910 {
5911     ChessMove moveType;
5912     ChessSquare pdown, pup;
5913
5914     /* Check if the user is playing in turn.  This is complicated because we
5915        let the user "pick up" a piece before it is his turn.  So the piece he
5916        tried to pick up may have been captured by the time he puts it down!
5917        Therefore we use the color the user is supposed to be playing in this
5918        test, not the color of the piece that is currently on the starting
5919        square---except in EditGame mode, where the user is playing both
5920        sides; fortunately there the capture race can't happen.  (It can
5921        now happen in IcsExamining mode, but that's just too bad.  The user
5922        will get a somewhat confusing message in that case.)
5923        */
5924
5925     switch (gameMode) {
5926       case PlayFromGameFile:
5927       case AnalyzeFile:
5928       case TwoMachinesPlay:
5929       case EndOfGame:
5930       case IcsObserving:
5931       case IcsIdle:
5932         /* We switched into a game mode where moves are not accepted,
5933            perhaps while the mouse button was down. */
5934         return;
5935
5936       case MachinePlaysWhite:
5937         /* User is moving for Black */
5938         if (WhiteOnMove(currentMove)) {
5939             DisplayMoveError(_("It is White's turn"));
5940             return;
5941         }
5942         break;
5943
5944       case MachinePlaysBlack:
5945         /* User is moving for White */
5946         if (!WhiteOnMove(currentMove)) {
5947             DisplayMoveError(_("It is Black's turn"));
5948             return;
5949         }
5950         break;
5951
5952       case EditGame:
5953       case IcsExamining:
5954       case BeginningOfGame:
5955       case AnalyzeMode:
5956       case Training:
5957         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
5958             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
5959             /* User is moving for Black */
5960             if (WhiteOnMove(currentMove)) {
5961                 DisplayMoveError(_("It is White's turn"));
5962                 return;
5963             }
5964         } else {
5965             /* User is moving for White */
5966             if (!WhiteOnMove(currentMove)) {
5967                 DisplayMoveError(_("It is Black's turn"));
5968                 return;
5969             }
5970         }
5971         break;
5972
5973       case IcsPlayingBlack:
5974         /* User is moving for Black */
5975         if (WhiteOnMove(currentMove)) {
5976             if (!appData.premove) {
5977                 DisplayMoveError(_("It is White's turn"));
5978             } else if (toX >= 0 && toY >= 0) {
5979                 premoveToX = toX;
5980                 premoveToY = toY;
5981                 premoveFromX = fromX;
5982                 premoveFromY = fromY;
5983                 premovePromoChar = promoChar;
5984                 gotPremove = 1;
5985                 if (appData.debugMode)
5986                     fprintf(debugFP, "Got premove: fromX %d,"
5987                             "fromY %d, toX %d, toY %d\n",
5988                             fromX, fromY, toX, toY);
5989             }
5990             return;
5991         }
5992         break;
5993
5994       case IcsPlayingWhite:
5995         /* User is moving for White */
5996         if (!WhiteOnMove(currentMove)) {
5997             if (!appData.premove) {
5998                 DisplayMoveError(_("It is Black's turn"));
5999             } else if (toX >= 0 && toY >= 0) {
6000                 premoveToX = toX;
6001                 premoveToY = toY;
6002                 premoveFromX = fromX;
6003                 premoveFromY = fromY;
6004                 premovePromoChar = promoChar;
6005                 gotPremove = 1;
6006                 if (appData.debugMode)
6007                     fprintf(debugFP, "Got premove: fromX %d,"
6008                             "fromY %d, toX %d, toY %d\n",
6009                             fromX, fromY, toX, toY);
6010             }
6011             return;
6012         }
6013         break;
6014
6015       default:
6016         break;
6017
6018       case EditPosition:
6019         /* EditPosition, empty square, or different color piece;
6020            click-click move is possible */
6021         if (toX == -2 || toY == -2) {
6022             boards[0][fromY][fromX] = EmptySquare;
6023             DrawPosition(FALSE, boards[currentMove]);
6024             return;
6025         } else if (toX >= 0 && toY >= 0) {
6026             boards[0][toY][toX] = boards[0][fromY][fromX];
6027             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6028                 if(boards[0][fromY][0] != EmptySquare) {
6029                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6030                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6031                 }
6032             } else
6033             if(fromX == BOARD_RGHT+1) {
6034                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6035                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6036                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6037                 }
6038             } else
6039             boards[0][fromY][fromX] = EmptySquare;
6040             DrawPosition(FALSE, boards[currentMove]);
6041             return;
6042         }
6043         return;
6044     }
6045
6046     if(toX < 0 || toY < 0) return;
6047     pdown = boards[currentMove][fromY][fromX];
6048     pup = boards[currentMove][toY][toX];
6049
6050     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6051     if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
6052          if( pup != EmptySquare ) return;
6053          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6054            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6055                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6056            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6057            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6058            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6059            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6060          fromY = DROP_RANK;
6061     }
6062
6063     /* [HGM] always test for legality, to get promotion info */
6064     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6065                                          fromY, fromX, toY, toX, promoChar);
6066     /* [HGM] but possibly ignore an IllegalMove result */
6067     if (appData.testLegality) {
6068         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6069             DisplayMoveError(_("Illegal move"));
6070             return;
6071         }
6072     }
6073
6074     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6075 }
6076
6077 /* Common tail of UserMoveEvent and DropMenuEvent */
6078 int
6079 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6080      ChessMove moveType;
6081      int fromX, fromY, toX, toY;
6082      /*char*/int promoChar;
6083 {
6084     char *bookHit = 0;
6085
6086     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6087         // [HGM] superchess: suppress promotions to non-available piece
6088         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6089         if(WhiteOnMove(currentMove)) {
6090             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6091         } else {
6092             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6093         }
6094     }
6095
6096     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6097        move type in caller when we know the move is a legal promotion */
6098     if(moveType == NormalMove && promoChar)
6099         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6100
6101     /* [HGM] <popupFix> The following if has been moved here from
6102        UserMoveEvent(). Because it seemed to belong here (why not allow
6103        piece drops in training games?), and because it can only be
6104        performed after it is known to what we promote. */
6105     if (gameMode == Training) {
6106       /* compare the move played on the board to the next move in the
6107        * game. If they match, display the move and the opponent's response.
6108        * If they don't match, display an error message.
6109        */
6110       int saveAnimate;
6111       Board testBoard;
6112       CopyBoard(testBoard, boards[currentMove]);
6113       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6114
6115       if (CompareBoards(testBoard, boards[currentMove+1])) {
6116         ForwardInner(currentMove+1);
6117
6118         /* Autoplay the opponent's response.
6119          * if appData.animate was TRUE when Training mode was entered,
6120          * the response will be animated.
6121          */
6122         saveAnimate = appData.animate;
6123         appData.animate = animateTraining;
6124         ForwardInner(currentMove+1);
6125         appData.animate = saveAnimate;
6126
6127         /* check for the end of the game */
6128         if (currentMove >= forwardMostMove) {
6129           gameMode = PlayFromGameFile;
6130           ModeHighlight();
6131           SetTrainingModeOff();
6132           DisplayInformation(_("End of game"));
6133         }
6134       } else {
6135         DisplayError(_("Incorrect move"), 0);
6136       }
6137       return 1;
6138     }
6139
6140   /* Ok, now we know that the move is good, so we can kill
6141      the previous line in Analysis Mode */
6142   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6143                                 && currentMove < forwardMostMove) {
6144     PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6145   }
6146
6147   /* If we need the chess program but it's dead, restart it */
6148   ResurrectChessProgram();
6149
6150   /* A user move restarts a paused game*/
6151   if (pausing)
6152     PauseEvent();
6153
6154   thinkOutput[0] = NULLCHAR;
6155
6156   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6157
6158   if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
6159
6160   if (gameMode == BeginningOfGame) {
6161     if (appData.noChessProgram) {
6162       gameMode = EditGame;
6163       SetGameInfo();
6164     } else {
6165       char buf[MSG_SIZ];
6166       gameMode = MachinePlaysBlack;
6167       StartClocks();
6168       SetGameInfo();
6169       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6170       DisplayTitle(buf);
6171       if (first.sendName) {
6172         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6173         SendToProgram(buf, &first);
6174       }
6175       StartClocks();
6176     }
6177     ModeHighlight();
6178   }
6179
6180   /* Relay move to ICS or chess engine */
6181   if (appData.icsActive) {
6182     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6183         gameMode == IcsExamining) {
6184       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6185         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6186         SendToICS("draw ");
6187         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6188       }
6189       // also send plain move, in case ICS does not understand atomic claims
6190       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6191       ics_user_moved = 1;
6192     }
6193   } else {
6194     if (first.sendTime && (gameMode == BeginningOfGame ||
6195                            gameMode == MachinePlaysWhite ||
6196                            gameMode == MachinePlaysBlack)) {
6197       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6198     }
6199     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6200          // [HGM] book: if program might be playing, let it use book
6201         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6202         first.maybeThinking = TRUE;
6203     } else SendMoveToProgram(forwardMostMove-1, &first);
6204     if (currentMove == cmailOldMove + 1) {
6205       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6206     }
6207   }
6208
6209   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6210
6211   switch (gameMode) {
6212   case EditGame:
6213     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6214     case MT_NONE:
6215     case MT_CHECK:
6216       break;
6217     case MT_CHECKMATE:
6218     case MT_STAINMATE:
6219       if (WhiteOnMove(currentMove)) {
6220         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6221       } else {
6222         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6223       }
6224       break;
6225     case MT_STALEMATE:
6226       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6227       break;
6228     }
6229     break;
6230
6231   case MachinePlaysBlack:
6232   case MachinePlaysWhite:
6233     /* disable certain menu options while machine is thinking */
6234     SetMachineThinkingEnables();
6235     break;
6236
6237   default:
6238     break;
6239   }
6240
6241   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6242
6243   if(bookHit) { // [HGM] book: simulate book reply
6244         static char bookMove[MSG_SIZ]; // a bit generous?
6245
6246         programStats.nodes = programStats.depth = programStats.time =
6247         programStats.score = programStats.got_only_move = 0;
6248         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6249
6250         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6251         strcat(bookMove, bookHit);
6252         HandleMachineMove(bookMove, &first);
6253   }
6254   return 1;
6255 }
6256
6257 void
6258 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6259      Board board;
6260      int flags;
6261      ChessMove kind;
6262      int rf, ff, rt, ft;
6263      VOIDSTAR closure;
6264 {
6265     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6266     Markers *m = (Markers *) closure;
6267     if(rf == fromY && ff == fromX)
6268         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6269                          || kind == WhiteCapturesEnPassant
6270                          || kind == BlackCapturesEnPassant);
6271     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6272 }
6273
6274 void
6275 MarkTargetSquares(int clear)
6276 {
6277   int x, y;
6278   if(!appData.markers || !appData.highlightDragging ||
6279      !appData.testLegality || gameMode == EditPosition) return;
6280   if(clear) {
6281     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6282   } else {
6283     int capt = 0;
6284     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6285     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6286       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6287       if(capt)
6288       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6289     }
6290   }
6291   DrawPosition(TRUE, NULL);
6292 }
6293
6294 void LeftClick(ClickType clickType, int xPix, int yPix)
6295 {
6296     int x, y;
6297     Boolean saveAnimate;
6298     static int second = 0, promotionChoice = 0, dragging = 0;
6299     char promoChoice = NULLCHAR;
6300
6301     if(appData.seekGraph && appData.icsActive && loggedOn &&
6302         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6303         SeekGraphClick(clickType, xPix, yPix, 0);
6304         return;
6305     }
6306
6307     if (clickType == Press) ErrorPopDown();
6308     MarkTargetSquares(1);
6309
6310     x = EventToSquare(xPix, BOARD_WIDTH);
6311     y = EventToSquare(yPix, BOARD_HEIGHT);
6312     if (!flipView && y >= 0) {
6313         y = BOARD_HEIGHT - 1 - y;
6314     }
6315     if (flipView && x >= 0) {
6316         x = BOARD_WIDTH - 1 - x;
6317     }
6318
6319     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6320         if(clickType == Release) return; // ignore upclick of click-click destination
6321         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6322         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6323         if(gameInfo.holdingsWidth &&
6324                 (WhiteOnMove(currentMove)
6325                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6326                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6327             // click in right holdings, for determining promotion piece
6328             ChessSquare p = boards[currentMove][y][x];
6329             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6330             if(p != EmptySquare) {
6331                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6332                 fromX = fromY = -1;
6333                 return;
6334             }
6335         }
6336         DrawPosition(FALSE, boards[currentMove]);
6337         return;
6338     }
6339
6340     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6341     if(clickType == Press
6342             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6343               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6344               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6345         return;
6346
6347     autoQueen = appData.alwaysPromoteToQueen;
6348
6349     if (fromX == -1) {
6350       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
6351         if (clickType == Press) {
6352             /* First square */
6353             if (OKToStartUserMove(x, y)) {
6354                 fromX = x;
6355                 fromY = y;
6356                 second = 0;
6357                 MarkTargetSquares(0);
6358                 DragPieceBegin(xPix, yPix); dragging = 1;
6359                 if (appData.highlightDragging) {
6360                     SetHighlights(x, y, -1, -1);
6361                 }
6362             }
6363         } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6364             DragPieceEnd(xPix, yPix); dragging = 0;
6365             DrawPosition(FALSE, NULL);
6366         }
6367         return;
6368       }
6369     }
6370
6371     /* fromX != -1 */
6372     if (clickType == Press && gameMode != EditPosition) {
6373         ChessSquare fromP;
6374         ChessSquare toP;
6375         int frc;
6376
6377         // ignore off-board to clicks
6378         if(y < 0 || x < 0) return;
6379
6380         /* Check if clicking again on the same color piece */
6381         fromP = boards[currentMove][fromY][fromX];
6382         toP = boards[currentMove][y][x];
6383         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom;
6384         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6385              WhitePawn <= toP && toP <= WhiteKing &&
6386              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6387              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6388             (BlackPawn <= fromP && fromP <= BlackKing &&
6389              BlackPawn <= toP && toP <= BlackKing &&
6390              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6391              !(fromP == BlackKing && toP == BlackRook && frc))) {
6392             /* Clicked again on same color piece -- changed his mind */
6393             second = (x == fromX && y == fromY);
6394            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6395             if (appData.highlightDragging) {
6396                 SetHighlights(x, y, -1, -1);
6397             } else {
6398                 ClearHighlights();
6399             }
6400             if (OKToStartUserMove(x, y)) {
6401                 fromX = x;
6402                 fromY = y; dragging = 1;
6403                 MarkTargetSquares(0);
6404                 DragPieceBegin(xPix, yPix);
6405             }
6406             return;
6407            }
6408         }
6409         // ignore clicks on holdings
6410         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6411     }
6412
6413     if (clickType == Release && x == fromX && y == fromY) {
6414         DragPieceEnd(xPix, yPix); dragging = 0;
6415         if (appData.animateDragging) {
6416             /* Undo animation damage if any */
6417             DrawPosition(FALSE, NULL);
6418         }
6419         if (second) {
6420             /* Second up/down in same square; just abort move */
6421             second = 0;
6422             fromX = fromY = -1;
6423             ClearHighlights();
6424             gotPremove = 0;
6425             ClearPremoveHighlights();
6426         } else {
6427             /* First upclick in same square; start click-click mode */
6428             SetHighlights(x, y, -1, -1);
6429         }
6430         return;
6431     }
6432
6433     /* we now have a different from- and (possibly off-board) to-square */
6434     /* Completed move */
6435     toX = x;
6436     toY = y;
6437     saveAnimate = appData.animate;
6438     if (clickType == Press) {
6439         /* Finish clickclick move */
6440         if (appData.animate || appData.highlightLastMove) {
6441             SetHighlights(fromX, fromY, toX, toY);
6442         } else {
6443             ClearHighlights();
6444         }
6445     } else {
6446         /* Finish drag move */
6447         if (appData.highlightLastMove) {
6448             SetHighlights(fromX, fromY, toX, toY);
6449         } else {
6450             ClearHighlights();
6451         }
6452         DragPieceEnd(xPix, yPix); dragging = 0;
6453         /* Don't animate move and drag both */
6454         appData.animate = FALSE;
6455     }
6456
6457     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6458     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6459         ChessSquare piece = boards[currentMove][fromY][fromX];
6460         if(gameMode == EditPosition && piece != EmptySquare &&
6461            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6462             int n;
6463
6464             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6465                 n = PieceToNumber(piece - (int)BlackPawn);
6466                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6467                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6468                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6469             } else
6470             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6471                 n = PieceToNumber(piece);
6472                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6473                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6474                 boards[currentMove][n][BOARD_WIDTH-2]++;
6475             }
6476             boards[currentMove][fromY][fromX] = EmptySquare;
6477         }
6478         ClearHighlights();
6479         fromX = fromY = -1;
6480         DrawPosition(TRUE, boards[currentMove]);
6481         return;
6482     }
6483
6484     // off-board moves should not be highlighted
6485     if(x < 0 || x < 0) ClearHighlights();
6486
6487     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6488         SetHighlights(fromX, fromY, toX, toY);
6489         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6490             // [HGM] super: promotion to captured piece selected from holdings
6491             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6492             promotionChoice = TRUE;
6493             // kludge follows to temporarily execute move on display, without promoting yet
6494             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6495             boards[currentMove][toY][toX] = p;
6496             DrawPosition(FALSE, boards[currentMove]);
6497             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6498             boards[currentMove][toY][toX] = q;
6499             DisplayMessage("Click in holdings to choose piece", "");
6500             return;
6501         }
6502         PromotionPopUp();
6503     } else {
6504         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6505         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6506         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6507         fromX = fromY = -1;
6508     }
6509     appData.animate = saveAnimate;
6510     if (appData.animate || appData.animateDragging) {
6511         /* Undo animation damage if needed */
6512         DrawPosition(FALSE, NULL);
6513     }
6514 }
6515
6516 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6517 {   // front-end-free part taken out of PieceMenuPopup
6518     int whichMenu; int xSqr, ySqr;
6519
6520     if(seekGraphUp) { // [HGM] seekgraph
6521         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6522         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6523         return -2;
6524     }
6525
6526     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6527          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6528         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6529         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6530         if(action == Press)   {
6531             originalFlip = flipView;
6532             flipView = !flipView; // temporarily flip board to see game from partners perspective
6533             DrawPosition(TRUE, partnerBoard);
6534             DisplayMessage(partnerStatus, "");
6535             partnerUp = TRUE;
6536         } else if(action == Release) {
6537             flipView = originalFlip;
6538             DrawPosition(TRUE, boards[currentMove]);
6539             partnerUp = FALSE;
6540         }
6541         return -2;
6542     }
6543
6544     xSqr = EventToSquare(x, BOARD_WIDTH);
6545     ySqr = EventToSquare(y, BOARD_HEIGHT);
6546     if (action == Release) UnLoadPV(); // [HGM] pv
6547     if (action != Press) return -2; // return code to be ignored
6548     switch (gameMode) {
6549       case IcsExamining:
6550         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6551       case EditPosition:
6552         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6553         if (xSqr < 0 || ySqr < 0) return -1;\r
6554         whichMenu = 0; // edit-position menu
6555         break;
6556       case IcsObserving:
6557         if(!appData.icsEngineAnalyze) return -1;
6558       case IcsPlayingWhite:
6559       case IcsPlayingBlack:
6560         if(!appData.zippyPlay) goto noZip;
6561       case AnalyzeMode:
6562       case AnalyzeFile:
6563       case MachinePlaysWhite:
6564       case MachinePlaysBlack:
6565       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6566         if (!appData.dropMenu) {
6567           LoadPV(x, y);
6568           return 2; // flag front-end to grab mouse events
6569         }
6570         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6571            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6572       case EditGame:
6573       noZip:
6574         if (xSqr < 0 || ySqr < 0) return -1;
6575         if (!appData.dropMenu || appData.testLegality &&
6576             gameInfo.variant != VariantBughouse &&
6577             gameInfo.variant != VariantCrazyhouse) return -1;
6578         whichMenu = 1; // drop menu
6579         break;
6580       default:
6581         return -1;
6582     }
6583
6584     if (((*fromX = xSqr) < 0) ||
6585         ((*fromY = ySqr) < 0)) {
6586         *fromX = *fromY = -1;
6587         return -1;
6588     }
6589     if (flipView)
6590       *fromX = BOARD_WIDTH - 1 - *fromX;
6591     else
6592       *fromY = BOARD_HEIGHT - 1 - *fromY;
6593
6594     return whichMenu;
6595 }
6596
6597 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6598 {
6599 //    char * hint = lastHint;
6600     FrontEndProgramStats stats;
6601
6602     stats.which = cps == &first ? 0 : 1;
6603     stats.depth = cpstats->depth;
6604     stats.nodes = cpstats->nodes;
6605     stats.score = cpstats->score;
6606     stats.time = cpstats->time;
6607     stats.pv = cpstats->movelist;
6608     stats.hint = lastHint;
6609     stats.an_move_index = 0;
6610     stats.an_move_count = 0;
6611
6612     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6613         stats.hint = cpstats->move_name;
6614         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6615         stats.an_move_count = cpstats->nr_moves;
6616     }
6617
6618     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
6619
6620     SetProgramStats( &stats );
6621 }
6622
6623 void
6624 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6625 {       // count all piece types
6626         int p, f, r;
6627         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6628         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6629         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6630                 p = board[r][f];
6631                 pCnt[p]++;
6632                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6633                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6634                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6635                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6636                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6637                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6638         }
6639 }
6640
6641 int
6642 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6643 {
6644         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6645         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6646
6647         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6648         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6649         if(myPawns == 2 && nMine == 3) // KPP
6650             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6651         if(myPawns == 1 && nMine == 2) // KP
6652             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6653         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6654             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6655         if(myPawns) return FALSE;
6656         if(pCnt[WhiteRook+side])
6657             return pCnt[BlackRook-side] ||
6658                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6659                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6660                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6661         if(pCnt[WhiteCannon+side]) {
6662             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6663             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6664         }
6665         if(pCnt[WhiteKnight+side])
6666             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6667         return FALSE;
6668 }
6669
6670 int
6671 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6672 {
6673         VariantClass v = gameInfo.variant;
6674
6675         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6676         if(v == VariantShatranj) return TRUE; // always winnable through baring
6677         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6678         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6679
6680         if(v == VariantXiangqi) {
6681                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6682
6683                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6684                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6685                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6686                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6687                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6688                 if(stale) // we have at least one last-rank P plus perhaps C
6689                     return majors // KPKX
6690                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6691                 else // KCA*E*
6692                     return pCnt[WhiteFerz+side] // KCAK
6693                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6694                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6695                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6696
6697         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6698                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6699
6700                 if(nMine == 1) return FALSE; // bare King
6701                 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
6702                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
6703                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
6704                 // by now we have King + 1 piece (or multiple Bishops on the same color)
6705                 if(pCnt[WhiteKnight+side])
6706                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
6707                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
6708                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
6709                 if(nBishops)
6710                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
6711                 if(pCnt[WhiteAlfil+side])
6712                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
6713                 if(pCnt[WhiteWazir+side])
6714                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
6715         }
6716
6717         return TRUE;
6718 }
6719
6720 int
6721 Adjudicate(ChessProgramState *cps)
6722 {       // [HGM] some adjudications useful with buggy engines
6723         // [HGM] adjudicate: made into separate routine, which now can be called after every move
6724         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
6725         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
6726         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
6727         int k, count = 0; static int bare = 1;
6728         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
6729         Boolean canAdjudicate = !appData.icsActive;
6730
6731         // most tests only when we understand the game, i.e. legality-checking on
6732             if( appData.testLegality )
6733             {   /* [HGM] Some more adjudications for obstinate engines */
6734                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
6735                 static int moveCount = 6;
6736                 ChessMove result;
6737                 char *reason = NULL;
6738
6739                 /* Count what is on board. */
6740                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
6741
6742                 /* Some material-based adjudications that have to be made before stalemate test */
6743                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
6744                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
6745                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
6746                      if(canAdjudicate && appData.checkMates) {
6747                          if(engineOpponent)
6748                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6749                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6750                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
6751                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
6752                          return 1;
6753                      }
6754                 }
6755
6756                 /* Bare King in Shatranj (loses) or Losers (wins) */
6757                 if( nrW == 1 || nrB == 1) {
6758                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
6759                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
6760                      if(canAdjudicate && appData.checkMates) {
6761                          if(engineOpponent)
6762                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
6763                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6764                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
6765                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6766                          return 1;
6767                      }
6768                   } else
6769                   if( gameInfo.variant == VariantShatranj && --bare < 0)
6770                   {    /* bare King */
6771                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
6772                         if(canAdjudicate && appData.checkMates) {
6773                             /* but only adjudicate if adjudication enabled */
6774                             if(engineOpponent)
6775                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
6776                             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6777                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
6778                                                         "Xboard adjudication: Bare king", GE_XBOARD );
6779                             return 1;
6780                         }
6781                   }
6782                 } else bare = 1;
6783
6784
6785             // don't wait for engine to announce game end if we can judge ourselves
6786             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
6787               case MT_CHECK:
6788                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
6789                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
6790                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
6791                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
6792                             checkCnt++;
6793                         if(checkCnt >= 2) {
6794                             reason = "Xboard adjudication: 3rd check";
6795                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
6796                             break;
6797                         }
6798                     }
6799                 }
6800               case MT_NONE:
6801               default:
6802                 break;
6803               case MT_STALEMATE:
6804               case MT_STAINMATE:
6805                 reason = "Xboard adjudication: Stalemate";
6806                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
6807                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
6808                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
6809                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
6810                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
6811                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
6812                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
6813                                                                         EP_CHECKMATE : EP_WINS);
6814                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
6815                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
6816                 }
6817                 break;
6818               case MT_CHECKMATE:
6819                 reason = "Xboard adjudication: Checkmate";
6820                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
6821                 break;
6822             }
6823
6824                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
6825                     case EP_STALEMATE:
6826                         result = GameIsDrawn; break;
6827                     case EP_CHECKMATE:
6828                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
6829                     case EP_WINS:
6830                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
6831                     default:
6832                         result = EndOfFile;
6833                 }
6834                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
6835                     if(engineOpponent)
6836                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6837                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6838                     GameEnds( result, reason, GE_XBOARD );
6839                     return 1;
6840                 }
6841
6842                 /* Next absolutely insufficient mating material. */
6843                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
6844                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
6845                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
6846
6847                      /* always flag draws, for judging claims */
6848                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
6849
6850                      if(canAdjudicate && appData.materialDraws) {
6851                          /* but only adjudicate them if adjudication enabled */
6852                          if(engineOpponent) {
6853                            SendToProgram("force\n", engineOpponent); // suppress reply
6854                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
6855                          }
6856                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6857                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
6858                          return 1;
6859                      }
6860                 }
6861
6862                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
6863                 if(gameInfo.variant == VariantXiangqi ?
6864                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
6865                  : nrW + nrB == 4 &&
6866                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
6867                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
6868                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
6869                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
6870                    ) ) {
6871                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
6872                      {    /* if the first 3 moves do not show a tactical win, declare draw */
6873                           if(engineOpponent) {
6874                             SendToProgram("force\n", engineOpponent); // suppress reply
6875                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6876                           }
6877                           ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6878                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
6879                           return 1;
6880                      }
6881                 } else moveCount = 6;
6882             }
6883         if (appData.debugMode) { int i;
6884             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
6885                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
6886                     appData.drawRepeats);
6887             for( i=forwardMostMove; i>=backwardMostMove; i-- )
6888               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
6889
6890         }
6891
6892         // Repetition draws and 50-move rule can be applied independently of legality testing
6893
6894                 /* Check for rep-draws */
6895                 count = 0;
6896                 for(k = forwardMostMove-2;
6897                     k>=backwardMostMove && k>=forwardMostMove-100 &&
6898                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
6899                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
6900                     k-=2)
6901                 {   int rights=0;
6902                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
6903                         /* compare castling rights */
6904                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
6905                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
6906                                 rights++; /* King lost rights, while rook still had them */
6907                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
6908                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
6909                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
6910                                    rights++; /* but at least one rook lost them */
6911                         }
6912                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
6913                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
6914                                 rights++;
6915                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
6916                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
6917                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
6918                                    rights++;
6919                         }
6920                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
6921                             && appData.drawRepeats > 1) {
6922                              /* adjudicate after user-specified nr of repeats */
6923                              int result = GameIsDrawn;
6924                              char *details = "XBoard adjudication: repetition draw";
6925                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
6926                                 // [HGM] xiangqi: check for forbidden perpetuals
6927                                 int m, ourPerpetual = 1, hisPerpetual = 1;
6928                                 for(m=forwardMostMove; m>k; m-=2) {
6929                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
6930                                         ourPerpetual = 0; // the current mover did not always check
6931                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
6932                                         hisPerpetual = 0; // the opponent did not always check
6933                                 }
6934                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
6935                                                                         ourPerpetual, hisPerpetual);
6936                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
6937                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6938                                     details = "Xboard adjudication: perpetual checking";
6939                                 } else
6940                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
6941                                     break; // (or we would have caught him before). Abort repetition-checking loop.
6942                                 } else
6943                                 // Now check for perpetual chases
6944                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
6945                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
6946                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
6947                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
6948                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
6949                                         details = "Xboard adjudication: perpetual chasing";
6950                                     } else
6951                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
6952                                         break; // Abort repetition-checking loop.
6953                                 }
6954                                 // if neither of us is checking or chasing all the time, or both are, it is draw
6955                              }
6956                              if(engineOpponent) {
6957                                SendToProgram("force\n", engineOpponent); // suppress reply
6958                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6959                              }
6960                              ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6961                              GameEnds( result, details, GE_XBOARD );
6962                              return 1;
6963                         }
6964                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
6965                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
6966                     }
6967                 }
6968
6969                 /* Now we test for 50-move draws. Determine ply count */
6970                 count = forwardMostMove;
6971                 /* look for last irreversble move */
6972                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
6973                     count--;
6974                 /* if we hit starting position, add initial plies */
6975                 if( count == backwardMostMove )
6976                     count -= initialRulePlies;
6977                 count = forwardMostMove - count;
6978                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
6979                         // adjust reversible move counter for checks in Xiangqi
6980                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
6981                         if(i < backwardMostMove) i = backwardMostMove;
6982                         while(i <= forwardMostMove) {
6983                                 lastCheck = inCheck; // check evasion does not count
6984                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
6985                                 if(inCheck || lastCheck) count--; // check does not count
6986                                 i++;
6987                         }
6988                 }
6989                 if( count >= 100)
6990                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
6991                          /* this is used to judge if draw claims are legal */
6992                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
6993                          if(engineOpponent) {
6994                            SendToProgram("force\n", engineOpponent); // suppress reply
6995                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
6996                          }
6997                          ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6998                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
6999                          return 1;
7000                 }
7001
7002                 /* if draw offer is pending, treat it as a draw claim
7003                  * when draw condition present, to allow engines a way to
7004                  * claim draws before making their move to avoid a race
7005                  * condition occurring after their move
7006                  */
7007                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7008                          char *p = NULL;
7009                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7010                              p = "Draw claim: 50-move rule";
7011                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7012                              p = "Draw claim: 3-fold repetition";
7013                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7014                              p = "Draw claim: insufficient mating material";
7015                          if( p != NULL && canAdjudicate) {
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, p, GE_XBOARD );
7022                              return 1;
7023                          }
7024                 }
7025
7026                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7027                     if(engineOpponent) {
7028                       SendToProgram("force\n", engineOpponent); // suppress reply
7029                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7030                     }
7031                     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7032                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7033                     return 1;
7034                 }
7035         return 0;
7036 }
7037
7038 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7039 {   // [HGM] book: this routine intercepts moves to simulate book replies
7040     char *bookHit = NULL;
7041
7042     //first determine if the incoming move brings opponent into his book
7043     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7044         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7045     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7046     if(bookHit != NULL && !cps->bookSuspend) {
7047         // make sure opponent is not going to reply after receiving move to book position
7048         SendToProgram("force\n", cps);
7049         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7050     }
7051     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7052     // now arrange restart after book miss
7053     if(bookHit) {
7054         // after a book hit we never send 'go', and the code after the call to this routine
7055         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7056         char buf[MSG_SIZ];
7057         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7058         SendToProgram(buf, cps);
7059         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7060     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7061         SendToProgram("go\n", cps);
7062         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7063     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7064         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7065             SendToProgram("go\n", cps);
7066         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7067     }
7068     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7069 }
7070
7071 char *savedMessage;
7072 ChessProgramState *savedState;
7073 void DeferredBookMove(void)
7074 {
7075         if(savedState->lastPing != savedState->lastPong)
7076                     ScheduleDelayedEvent(DeferredBookMove, 10);
7077         else
7078         HandleMachineMove(savedMessage, savedState);
7079 }
7080
7081 void
7082 HandleMachineMove(message, cps)
7083      char *message;
7084      ChessProgramState *cps;
7085 {
7086     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7087     char realname[MSG_SIZ];
7088     int fromX, fromY, toX, toY;
7089     ChessMove moveType;
7090     char promoChar;
7091     char *p;
7092     int machineWhite;
7093     char *bookHit;
7094
7095     cps->userError = 0;
7096
7097 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7098     /*
7099      * Kludge to ignore BEL characters
7100      */
7101     while (*message == '\007') message++;
7102
7103     /*
7104      * [HGM] engine debug message: ignore lines starting with '#' character
7105      */
7106     if(cps->debug && *message == '#') return;
7107
7108     /*
7109      * Look for book output
7110      */
7111     if (cps == &first && bookRequested) {
7112         if (message[0] == '\t' || message[0] == ' ') {
7113             /* Part of the book output is here; append it */
7114             strcat(bookOutput, message);
7115             strcat(bookOutput, "  \n");
7116             return;
7117         } else if (bookOutput[0] != NULLCHAR) {
7118             /* All of book output has arrived; display it */
7119             char *p = bookOutput;
7120             while (*p != NULLCHAR) {
7121                 if (*p == '\t') *p = ' ';
7122                 p++;
7123             }
7124             DisplayInformation(bookOutput);
7125             bookRequested = FALSE;
7126             /* Fall through to parse the current output */
7127         }
7128     }
7129
7130     /*
7131      * Look for machine move.
7132      */
7133     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7134         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7135     {
7136         /* This method is only useful on engines that support ping */
7137         if (cps->lastPing != cps->lastPong) {
7138           if (gameMode == BeginningOfGame) {
7139             /* Extra move from before last new; ignore */
7140             if (appData.debugMode) {
7141                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7142             }
7143           } else {
7144             if (appData.debugMode) {
7145                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7146                         cps->which, gameMode);
7147             }
7148
7149             SendToProgram("undo\n", cps);
7150           }
7151           return;
7152         }
7153
7154         switch (gameMode) {
7155           case BeginningOfGame:
7156             /* Extra move from before last reset; ignore */
7157             if (appData.debugMode) {
7158                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7159             }
7160             return;
7161
7162           case EndOfGame:
7163           case IcsIdle:
7164           default:
7165             /* Extra move after we tried to stop.  The mode test is
7166                not a reliable way of detecting this problem, but it's
7167                the best we can do on engines that don't support ping.
7168             */
7169             if (appData.debugMode) {
7170                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7171                         cps->which, gameMode);
7172             }
7173             SendToProgram("undo\n", cps);
7174             return;
7175
7176           case MachinePlaysWhite:
7177           case IcsPlayingWhite:
7178             machineWhite = TRUE;
7179             break;
7180
7181           case MachinePlaysBlack:
7182           case IcsPlayingBlack:
7183             machineWhite = FALSE;
7184             break;
7185
7186           case TwoMachinesPlay:
7187             machineWhite = (cps->twoMachinesColor[0] == 'w');
7188             break;
7189         }
7190         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7191             if (appData.debugMode) {
7192                 fprintf(debugFP,
7193                         "Ignoring move out of turn by %s, gameMode %d"
7194                         ", forwardMost %d\n",
7195                         cps->which, gameMode, forwardMostMove);
7196             }
7197             return;
7198         }
7199
7200     if (appData.debugMode) { int f = forwardMostMove;
7201         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7202                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7203                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7204     }
7205         if(cps->alphaRank) AlphaRank(machineMove, 4);
7206         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7207                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7208             /* Machine move could not be parsed; ignore it. */
7209           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7210                     machineMove, cps->which);
7211             DisplayError(buf1, 0);
7212             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7213                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7214             if (gameMode == TwoMachinesPlay) {
7215               GameEnds(machineWhite ? BlackWins : WhiteWins,
7216                        buf1, GE_XBOARD);
7217             }
7218             return;
7219         }
7220
7221         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7222         /* So we have to redo legality test with true e.p. status here,  */
7223         /* to make sure an illegal e.p. capture does not slip through,   */
7224         /* to cause a forfeit on a justified illegal-move complaint      */
7225         /* of the opponent.                                              */
7226         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7227            ChessMove moveType;
7228            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7229                              fromY, fromX, toY, toX, promoChar);
7230             if (appData.debugMode) {
7231                 int i;
7232                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7233                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7234                 fprintf(debugFP, "castling rights\n");
7235             }
7236             if(moveType == IllegalMove) {
7237               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7238                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7239                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7240                            buf1, GE_XBOARD);
7241                 return;
7242            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7243            /* [HGM] Kludge to handle engines that send FRC-style castling
7244               when they shouldn't (like TSCP-Gothic) */
7245            switch(moveType) {
7246              case WhiteASideCastleFR:
7247              case BlackASideCastleFR:
7248                toX+=2;
7249                currentMoveString[2]++;
7250                break;
7251              case WhiteHSideCastleFR:
7252              case BlackHSideCastleFR:
7253                toX--;
7254                currentMoveString[2]--;
7255                break;
7256              default: ; // nothing to do, but suppresses warning of pedantic compilers
7257            }
7258         }
7259         hintRequested = FALSE;
7260         lastHint[0] = NULLCHAR;
7261         bookRequested = FALSE;
7262         /* Program may be pondering now */
7263         cps->maybeThinking = TRUE;
7264         if (cps->sendTime == 2) cps->sendTime = 1;
7265         if (cps->offeredDraw) cps->offeredDraw--;
7266
7267         /* currentMoveString is set as a side-effect of ParseOneMove */
7268         safeStrCpy(machineMove, currentMoveString, sizeof(machineMove)/sizeof(machineMove[0]));
7269         strcat(machineMove, "\n");
7270         safeStrCpy(moveList[forwardMostMove], machineMove, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
7271
7272         /* [AS] Save move info*/
7273         pvInfoList[ forwardMostMove ].score = programStats.score;
7274         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7275         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7276
7277         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7278
7279         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7280         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7281             int count = 0;
7282
7283             while( count < adjudicateLossPlies ) {
7284                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7285
7286                 if( count & 1 ) {
7287                     score = -score; /* Flip score for winning side */
7288                 }
7289
7290                 if( score > adjudicateLossThreshold ) {
7291                     break;
7292                 }
7293
7294                 count++;
7295             }
7296
7297             if( count >= adjudicateLossPlies ) {
7298                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7299
7300                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7301                     "Xboard adjudication",
7302                     GE_XBOARD );
7303
7304                 return;
7305             }
7306         }
7307
7308         if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
7309
7310 #if ZIPPY
7311         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7312             first.initDone) {
7313           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7314                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7315                 SendToICS("draw ");
7316                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7317           }
7318           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7319           ics_user_moved = 1;
7320           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7321                 char buf[3*MSG_SIZ];
7322
7323                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7324                         programStats.score / 100.,
7325                         programStats.depth,
7326                         programStats.time / 100.,
7327                         (unsigned int)programStats.nodes,
7328                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7329                         programStats.movelist);
7330                 SendToICS(buf);
7331 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7332           }
7333         }
7334 #endif
7335
7336         /* [AS] Clear stats for next move */
7337         ClearProgramStats();
7338         thinkOutput[0] = NULLCHAR;
7339         hiddenThinkOutputState = 0;
7340
7341         bookHit = NULL;
7342         if (gameMode == TwoMachinesPlay) {
7343             /* [HGM] relaying draw offers moved to after reception of move */
7344             /* and interpreting offer as claim if it brings draw condition */
7345             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7346                 SendToProgram("draw\n", cps->other);
7347             }
7348             if (cps->other->sendTime) {
7349                 SendTimeRemaining(cps->other,
7350                                   cps->other->twoMachinesColor[0] == 'w');
7351             }
7352             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7353             if (firstMove && !bookHit) {
7354                 firstMove = FALSE;
7355                 if (cps->other->useColors) {
7356                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7357                 }
7358                 SendToProgram("go\n", cps->other);
7359             }
7360             cps->other->maybeThinking = TRUE;
7361         }
7362
7363         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7364
7365         if (!pausing && appData.ringBellAfterMoves) {
7366             RingBell();
7367         }
7368
7369         /*
7370          * Reenable menu items that were disabled while
7371          * machine was thinking
7372          */
7373         if (gameMode != TwoMachinesPlay)
7374             SetUserThinkingEnables();
7375
7376         // [HGM] book: after book hit opponent has received move and is now in force mode
7377         // force the book reply into it, and then fake that it outputted this move by jumping
7378         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7379         if(bookHit) {
7380                 static char bookMove[MSG_SIZ]; // a bit generous?
7381
7382                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7383                 strcat(bookMove, bookHit);
7384                 message = bookMove;
7385                 cps = cps->other;
7386                 programStats.nodes = programStats.depth = programStats.time =
7387                 programStats.score = programStats.got_only_move = 0;
7388                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7389
7390                 if(cps->lastPing != cps->lastPong) {
7391                     savedMessage = message; // args for deferred call
7392                     savedState = cps;
7393                     ScheduleDelayedEvent(DeferredBookMove, 10);
7394                     return;
7395                 }
7396                 goto FakeBookMove;
7397         }
7398
7399         return;
7400     }
7401
7402     /* Set special modes for chess engines.  Later something general
7403      *  could be added here; for now there is just one kludge feature,
7404      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7405      *  when "xboard" is given as an interactive command.
7406      */
7407     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7408         cps->useSigint = FALSE;
7409         cps->useSigterm = FALSE;
7410     }
7411     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7412       ParseFeatures(message+8, cps);
7413       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7414     }
7415
7416     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7417      * want this, I was asked to put it in, and obliged.
7418      */
7419     if (!strncmp(message, "setboard ", 9)) {
7420         Board initial_position;
7421
7422         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7423
7424         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7425             DisplayError(_("Bad FEN received from engine"), 0);
7426             return ;
7427         } else {
7428            Reset(TRUE, FALSE);
7429            CopyBoard(boards[0], initial_position);
7430            initialRulePlies = FENrulePlies;
7431            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7432            else gameMode = MachinePlaysBlack;
7433            DrawPosition(FALSE, boards[currentMove]);
7434         }
7435         return;
7436     }
7437
7438     /*
7439      * Look for communication commands
7440      */
7441     if (!strncmp(message, "telluser ", 9)) {
7442         EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
7443         DisplayNote(message + 9);
7444         return;
7445     }
7446     if (!strncmp(message, "tellusererror ", 14)) {
7447         cps->userError = 1;
7448         EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
7449         DisplayError(message + 14, 0);
7450         return;
7451     }
7452     if (!strncmp(message, "tellopponent ", 13)) {
7453       if (appData.icsActive) {
7454         if (loggedOn) {
7455           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7456           SendToICS(buf1);
7457         }
7458       } else {
7459         DisplayNote(message + 13);
7460       }
7461       return;
7462     }
7463     if (!strncmp(message, "tellothers ", 11)) {
7464       if (appData.icsActive) {
7465         if (loggedOn) {
7466           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7467           SendToICS(buf1);
7468         }
7469       }
7470       return;
7471     }
7472     if (!strncmp(message, "tellall ", 8)) {
7473       if (appData.icsActive) {
7474         if (loggedOn) {
7475           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7476           SendToICS(buf1);
7477         }
7478       } else {
7479         DisplayNote(message + 8);
7480       }
7481       return;
7482     }
7483     if (strncmp(message, "warning", 7) == 0) {
7484         /* Undocumented feature, use tellusererror in new code */
7485         DisplayError(message, 0);
7486         return;
7487     }
7488     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7489         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7490         strcat(realname, " query");
7491         AskQuestion(realname, buf2, buf1, cps->pr);
7492         return;
7493     }
7494     /* Commands from the engine directly to ICS.  We don't allow these to be
7495      *  sent until we are logged on. Crafty kibitzes have been known to
7496      *  interfere with the login process.
7497      */
7498     if (loggedOn) {
7499         if (!strncmp(message, "tellics ", 8)) {
7500             SendToICS(message + 8);
7501             SendToICS("\n");
7502             return;
7503         }
7504         if (!strncmp(message, "tellicsnoalias ", 15)) {
7505             SendToICS(ics_prefix);
7506             SendToICS(message + 15);
7507             SendToICS("\n");
7508             return;
7509         }
7510         /* The following are for backward compatibility only */
7511         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7512             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7513             SendToICS(ics_prefix);
7514             SendToICS(message);
7515             SendToICS("\n");
7516             return;
7517         }
7518     }
7519     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7520         return;
7521     }
7522     /*
7523      * If the move is illegal, cancel it and redraw the board.
7524      * Also deal with other error cases.  Matching is rather loose
7525      * here to accommodate engines written before the spec.
7526      */
7527     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7528         strncmp(message, "Error", 5) == 0) {
7529         if (StrStr(message, "name") ||
7530             StrStr(message, "rating") || StrStr(message, "?") ||
7531             StrStr(message, "result") || StrStr(message, "board") ||
7532             StrStr(message, "bk") || StrStr(message, "computer") ||
7533             StrStr(message, "variant") || StrStr(message, "hint") ||
7534             StrStr(message, "random") || StrStr(message, "depth") ||
7535             StrStr(message, "accepted")) {
7536             return;
7537         }
7538         if (StrStr(message, "protover")) {
7539           /* Program is responding to input, so it's apparently done
7540              initializing, and this error message indicates it is
7541              protocol version 1.  So we don't need to wait any longer
7542              for it to initialize and send feature commands. */
7543           FeatureDone(cps, 1);
7544           cps->protocolVersion = 1;
7545           return;
7546         }
7547         cps->maybeThinking = FALSE;
7548
7549         if (StrStr(message, "draw")) {
7550             /* Program doesn't have "draw" command */
7551             cps->sendDrawOffers = 0;
7552             return;
7553         }
7554         if (cps->sendTime != 1 &&
7555             (StrStr(message, "time") || StrStr(message, "otim"))) {
7556           /* Program apparently doesn't have "time" or "otim" command */
7557           cps->sendTime = 0;
7558           return;
7559         }
7560         if (StrStr(message, "analyze")) {
7561             cps->analysisSupport = FALSE;
7562             cps->analyzing = FALSE;
7563             Reset(FALSE, TRUE);
7564             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7565             DisplayError(buf2, 0);
7566             return;
7567         }
7568         if (StrStr(message, "(no matching move)st")) {
7569           /* Special kludge for GNU Chess 4 only */
7570           cps->stKludge = TRUE;
7571           SendTimeControl(cps, movesPerSession, timeControl,
7572                           timeIncrement, appData.searchDepth,
7573                           searchTime);
7574           return;
7575         }
7576         if (StrStr(message, "(no matching move)sd")) {
7577           /* Special kludge for GNU Chess 4 only */
7578           cps->sdKludge = TRUE;
7579           SendTimeControl(cps, movesPerSession, timeControl,
7580                           timeIncrement, appData.searchDepth,
7581                           searchTime);
7582           return;
7583         }
7584         if (!StrStr(message, "llegal")) {
7585             return;
7586         }
7587         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7588             gameMode == IcsIdle) return;
7589         if (forwardMostMove <= backwardMostMove) return;
7590         if (pausing) PauseEvent();
7591       if(appData.forceIllegal) {
7592             // [HGM] illegal: machine refused move; force position after move into it
7593           SendToProgram("force\n", cps);
7594           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7595                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7596                 // when black is to move, while there might be nothing on a2 or black
7597                 // might already have the move. So send the board as if white has the move.
7598                 // But first we must change the stm of the engine, as it refused the last move
7599                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7600                 if(WhiteOnMove(forwardMostMove)) {
7601                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7602                     SendBoard(cps, forwardMostMove); // kludgeless board
7603                 } else {
7604                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7605                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7606                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7607                 }
7608           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7609             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7610                  gameMode == TwoMachinesPlay)
7611               SendToProgram("go\n", cps);
7612             return;
7613       } else
7614         if (gameMode == PlayFromGameFile) {
7615             /* Stop reading this game file */
7616             gameMode = EditGame;
7617             ModeHighlight();
7618         }
7619         currentMove = forwardMostMove-1;
7620         DisplayMove(currentMove-1); /* before DisplayMoveError */
7621         SwitchClocks(forwardMostMove-1); // [HGM] race
7622         DisplayBothClocks();
7623         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7624                 parseList[currentMove], cps->which);
7625         DisplayMoveError(buf1);
7626         DrawPosition(FALSE, boards[currentMove]);
7627
7628         /* [HGM] illegal-move claim should forfeit game when Xboard */
7629         /* only passes fully legal moves                            */
7630         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7631             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7632                                 "False illegal-move claim", GE_XBOARD );
7633         }
7634         return;
7635     }
7636     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7637         /* Program has a broken "time" command that
7638            outputs a string not ending in newline.
7639            Don't use it. */
7640         cps->sendTime = 0;
7641     }
7642
7643     /*
7644      * If chess program startup fails, exit with an error message.
7645      * Attempts to recover here are futile.
7646      */
7647     if ((StrStr(message, "unknown host") != NULL)
7648         || (StrStr(message, "No remote directory") != NULL)
7649         || (StrStr(message, "not found") != NULL)
7650         || (StrStr(message, "No such file") != NULL)
7651         || (StrStr(message, "can't alloc") != NULL)
7652         || (StrStr(message, "Permission denied") != NULL)) {
7653
7654         cps->maybeThinking = FALSE;
7655         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7656                 cps->which, cps->program, cps->host, message);
7657         RemoveInputSource(cps->isr);
7658         DisplayFatalError(buf1, 0, 1);
7659         return;
7660     }
7661
7662     /*
7663      * Look for hint output
7664      */
7665     if (sscanf(message, "Hint: %s", buf1) == 1) {
7666         if (cps == &first && hintRequested) {
7667             hintRequested = FALSE;
7668             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7669                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7670                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7671                                     PosFlags(forwardMostMove),
7672                                     fromY, fromX, toY, toX, promoChar, buf1);
7673                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7674                 DisplayInformation(buf2);
7675             } else {
7676                 /* Hint move could not be parsed!? */
7677               snprintf(buf2, sizeof(buf2),
7678                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7679                         buf1, cps->which);
7680                 DisplayError(buf2, 0);
7681             }
7682         } else {
7683           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7684         }
7685         return;
7686     }
7687
7688     /*
7689      * Ignore other messages if game is not in progress
7690      */
7691     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7692         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7693
7694     /*
7695      * look for win, lose, draw, or draw offer
7696      */
7697     if (strncmp(message, "1-0", 3) == 0) {
7698         char *p, *q, *r = "";
7699         p = strchr(message, '{');
7700         if (p) {
7701             q = strchr(p, '}');
7702             if (q) {
7703                 *q = NULLCHAR;
7704                 r = p + 1;
7705             }
7706         }
7707         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
7708         return;
7709     } else if (strncmp(message, "0-1", 3) == 0) {
7710         char *p, *q, *r = "";
7711         p = strchr(message, '{');
7712         if (p) {
7713             q = strchr(p, '}');
7714             if (q) {
7715                 *q = NULLCHAR;
7716                 r = p + 1;
7717             }
7718         }
7719         /* Kludge for Arasan 4.1 bug */
7720         if (strcmp(r, "Black resigns") == 0) {
7721             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
7722             return;
7723         }
7724         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
7725         return;
7726     } else if (strncmp(message, "1/2", 3) == 0) {
7727         char *p, *q, *r = "";
7728         p = strchr(message, '{');
7729         if (p) {
7730             q = strchr(p, '}');
7731             if (q) {
7732                 *q = NULLCHAR;
7733                 r = p + 1;
7734             }
7735         }
7736
7737         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
7738         return;
7739
7740     } else if (strncmp(message, "White resign", 12) == 0) {
7741         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7742         return;
7743     } else if (strncmp(message, "Black resign", 12) == 0) {
7744         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7745         return;
7746     } else if (strncmp(message, "White matches", 13) == 0 ||
7747                strncmp(message, "Black matches", 13) == 0   ) {
7748         /* [HGM] ignore GNUShogi noises */
7749         return;
7750     } else if (strncmp(message, "White", 5) == 0 &&
7751                message[5] != '(' &&
7752                StrStr(message, "Black") == NULL) {
7753         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7754         return;
7755     } else if (strncmp(message, "Black", 5) == 0 &&
7756                message[5] != '(') {
7757         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7758         return;
7759     } else if (strcmp(message, "resign") == 0 ||
7760                strcmp(message, "computer resigns") == 0) {
7761         switch (gameMode) {
7762           case MachinePlaysBlack:
7763           case IcsPlayingBlack:
7764             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
7765             break;
7766           case MachinePlaysWhite:
7767           case IcsPlayingWhite:
7768             GameEnds(BlackWins, "White resigns", GE_ENGINE);
7769             break;
7770           case TwoMachinesPlay:
7771             if (cps->twoMachinesColor[0] == 'w')
7772               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
7773             else
7774               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
7775             break;
7776           default:
7777             /* can't happen */
7778             break;
7779         }
7780         return;
7781     } else if (strncmp(message, "opponent mates", 14) == 0) {
7782         switch (gameMode) {
7783           case MachinePlaysBlack:
7784           case IcsPlayingBlack:
7785             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7786             break;
7787           case MachinePlaysWhite:
7788           case IcsPlayingWhite:
7789             GameEnds(BlackWins, "Black mates", GE_ENGINE);
7790             break;
7791           case TwoMachinesPlay:
7792             if (cps->twoMachinesColor[0] == 'w')
7793               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7794             else
7795               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7796             break;
7797           default:
7798             /* can't happen */
7799             break;
7800         }
7801         return;
7802     } else if (strncmp(message, "computer mates", 14) == 0) {
7803         switch (gameMode) {
7804           case MachinePlaysBlack:
7805           case IcsPlayingBlack:
7806             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
7807             break;
7808           case MachinePlaysWhite:
7809           case IcsPlayingWhite:
7810             GameEnds(WhiteWins, "White mates", GE_ENGINE);
7811             break;
7812           case TwoMachinesPlay:
7813             if (cps->twoMachinesColor[0] == 'w')
7814               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7815             else
7816               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7817             break;
7818           default:
7819             /* can't happen */
7820             break;
7821         }
7822         return;
7823     } else if (strncmp(message, "checkmate", 9) == 0) {
7824         if (WhiteOnMove(forwardMostMove)) {
7825             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
7826         } else {
7827             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
7828         }
7829         return;
7830     } else if (strstr(message, "Draw") != NULL ||
7831                strstr(message, "game is a draw") != NULL) {
7832         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
7833         return;
7834     } else if (strstr(message, "offer") != NULL &&
7835                strstr(message, "draw") != NULL) {
7836 #if ZIPPY
7837         if (appData.zippyPlay && first.initDone) {
7838             /* Relay offer to ICS */
7839             SendToICS(ics_prefix);
7840             SendToICS("draw\n");
7841         }
7842 #endif
7843         cps->offeredDraw = 2; /* valid until this engine moves twice */
7844         if (gameMode == TwoMachinesPlay) {
7845             if (cps->other->offeredDraw) {
7846                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7847             /* [HGM] in two-machine mode we delay relaying draw offer      */
7848             /* until after we also have move, to see if it is really claim */
7849             }
7850         } else if (gameMode == MachinePlaysWhite ||
7851                    gameMode == MachinePlaysBlack) {
7852           if (userOfferedDraw) {
7853             DisplayInformation(_("Machine accepts your draw offer"));
7854             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
7855           } else {
7856             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
7857           }
7858         }
7859     }
7860
7861
7862     /*
7863      * Look for thinking output
7864      */
7865     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
7866           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
7867                                 ) {
7868         int plylev, mvleft, mvtot, curscore, time;
7869         char mvname[MOVE_LEN];
7870         u64 nodes; // [DM]
7871         char plyext;
7872         int ignore = FALSE;
7873         int prefixHint = FALSE;
7874         mvname[0] = NULLCHAR;
7875
7876         switch (gameMode) {
7877           case MachinePlaysBlack:
7878           case IcsPlayingBlack:
7879             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7880             break;
7881           case MachinePlaysWhite:
7882           case IcsPlayingWhite:
7883             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
7884             break;
7885           case AnalyzeMode:
7886           case AnalyzeFile:
7887             break;
7888           case IcsObserving: /* [DM] icsEngineAnalyze */
7889             if (!appData.icsEngineAnalyze) ignore = TRUE;
7890             break;
7891           case TwoMachinesPlay:
7892             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
7893                 ignore = TRUE;
7894             }
7895             break;
7896           default:
7897             ignore = TRUE;
7898             break;
7899         }
7900
7901         if (!ignore) {
7902             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
7903             buf1[0] = NULLCHAR;
7904             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
7905                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
7906
7907                 if (plyext != ' ' && plyext != '\t') {
7908                     time *= 100;
7909                 }
7910
7911                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
7912                 if( cps->scoreIsAbsolute &&
7913                     ( gameMode == MachinePlaysBlack ||
7914                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
7915                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
7916                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
7917                      !WhiteOnMove(currentMove)
7918                     ) )
7919                 {
7920                     curscore = -curscore;
7921                 }
7922
7923
7924                 tempStats.depth = plylev;
7925                 tempStats.nodes = nodes;
7926                 tempStats.time = time;
7927                 tempStats.score = curscore;
7928                 tempStats.got_only_move = 0;
7929
7930                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
7931                         int ticklen;
7932
7933                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
7934                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
7935                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
7936                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
7937                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
7938                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
7939                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
7940                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
7941                 }
7942
7943                 /* Buffer overflow protection */
7944                 if (buf1[0] != NULLCHAR) {
7945                     if (strlen(buf1) >= sizeof(tempStats.movelist)
7946                         && appData.debugMode) {
7947                         fprintf(debugFP,
7948                                 "PV is too long; using the first %u bytes.\n",
7949                                 (unsigned) sizeof(tempStats.movelist) - 1);
7950                     }
7951
7952                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
7953                 } else {
7954                     sprintf(tempStats.movelist, " no PV\n");
7955                 }
7956
7957                 if (tempStats.seen_stat) {
7958                     tempStats.ok_to_send = 1;
7959                 }
7960
7961                 if (strchr(tempStats.movelist, '(') != NULL) {
7962                     tempStats.line_is_book = 1;
7963                     tempStats.nr_moves = 0;
7964                     tempStats.moves_left = 0;
7965                 } else {
7966                     tempStats.line_is_book = 0;
7967                 }
7968
7969                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
7970                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
7971
7972                 SendProgramStatsToFrontend( cps, &tempStats );
7973
7974                 /*
7975                     [AS] Protect the thinkOutput buffer from overflow... this
7976                     is only useful if buf1 hasn't overflowed first!
7977                 */
7978                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
7979                          plylev,
7980                          (gameMode == TwoMachinesPlay ?
7981                           ToUpper(cps->twoMachinesColor[0]) : ' '),
7982                          ((double) curscore) / 100.0,
7983                          prefixHint ? lastHint : "",
7984                          prefixHint ? " " : "" );
7985
7986                 if( buf1[0] != NULLCHAR ) {
7987                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
7988
7989                     if( strlen(buf1) > max_len ) {
7990                         if( appData.debugMode) {
7991                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
7992                         }
7993                         buf1[max_len+1] = '\0';
7994                     }
7995
7996                     strcat( thinkOutput, buf1 );
7997                 }
7998
7999                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8000                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8001                     DisplayMove(currentMove - 1);
8002                 }
8003                 return;
8004
8005             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8006                 /* crafty (9.25+) says "(only move) <move>"
8007                  * if there is only 1 legal move
8008                  */
8009                 sscanf(p, "(only move) %s", buf1);
8010                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8011                 sprintf(programStats.movelist, "%s (only move)", buf1);
8012                 programStats.depth = 1;
8013                 programStats.nr_moves = 1;
8014                 programStats.moves_left = 1;
8015                 programStats.nodes = 1;
8016                 programStats.time = 1;
8017                 programStats.got_only_move = 1;
8018
8019                 /* Not really, but we also use this member to
8020                    mean "line isn't going to change" (Crafty
8021                    isn't searching, so stats won't change) */
8022                 programStats.line_is_book = 1;
8023
8024                 SendProgramStatsToFrontend( cps, &programStats );
8025
8026                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8027                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8028                     DisplayMove(currentMove - 1);
8029                 }
8030                 return;
8031             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8032                               &time, &nodes, &plylev, &mvleft,
8033                               &mvtot, mvname) >= 5) {
8034                 /* The stat01: line is from Crafty (9.29+) in response
8035                    to the "." command */
8036                 programStats.seen_stat = 1;
8037                 cps->maybeThinking = TRUE;
8038
8039                 if (programStats.got_only_move || !appData.periodicUpdates)
8040                   return;
8041
8042                 programStats.depth = plylev;
8043                 programStats.time = time;
8044                 programStats.nodes = nodes;
8045                 programStats.moves_left = mvleft;
8046                 programStats.nr_moves = mvtot;
8047                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8048                 programStats.ok_to_send = 1;
8049                 programStats.movelist[0] = '\0';
8050
8051                 SendProgramStatsToFrontend( cps, &programStats );
8052
8053                 return;
8054
8055             } else if (strncmp(message,"++",2) == 0) {
8056                 /* Crafty 9.29+ outputs this */
8057                 programStats.got_fail = 2;
8058                 return;
8059
8060             } else if (strncmp(message,"--",2) == 0) {
8061                 /* Crafty 9.29+ outputs this */
8062                 programStats.got_fail = 1;
8063                 return;
8064
8065             } else if (thinkOutput[0] != NULLCHAR &&
8066                        strncmp(message, "    ", 4) == 0) {
8067                 unsigned message_len;
8068
8069                 p = message;
8070                 while (*p && *p == ' ') p++;
8071
8072                 message_len = strlen( p );
8073
8074                 /* [AS] Avoid buffer overflow */
8075                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8076                     strcat(thinkOutput, " ");
8077                     strcat(thinkOutput, p);
8078                 }
8079
8080                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8081                     strcat(programStats.movelist, " ");
8082                     strcat(programStats.movelist, p);
8083                 }
8084
8085                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8086                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8087                     DisplayMove(currentMove - 1);
8088                 }
8089                 return;
8090             }
8091         }
8092         else {
8093             buf1[0] = NULLCHAR;
8094
8095             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8096                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8097             {
8098                 ChessProgramStats cpstats;
8099
8100                 if (plyext != ' ' && plyext != '\t') {
8101                     time *= 100;
8102                 }
8103
8104                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8105                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8106                     curscore = -curscore;
8107                 }
8108
8109                 cpstats.depth = plylev;
8110                 cpstats.nodes = nodes;
8111                 cpstats.time = time;
8112                 cpstats.score = curscore;
8113                 cpstats.got_only_move = 0;
8114                 cpstats.movelist[0] = '\0';
8115
8116                 if (buf1[0] != NULLCHAR) {
8117                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8118                 }
8119
8120                 cpstats.ok_to_send = 0;
8121                 cpstats.line_is_book = 0;
8122                 cpstats.nr_moves = 0;
8123                 cpstats.moves_left = 0;
8124
8125                 SendProgramStatsToFrontend( cps, &cpstats );
8126             }
8127         }
8128     }
8129 }
8130
8131
8132 /* Parse a game score from the character string "game", and
8133    record it as the history of the current game.  The game
8134    score is NOT assumed to start from the standard position.
8135    The display is not updated in any way.
8136    */
8137 void
8138 ParseGameHistory(game)
8139      char *game;
8140 {
8141     ChessMove moveType;
8142     int fromX, fromY, toX, toY, boardIndex;
8143     char promoChar;
8144     char *p, *q;
8145     char buf[MSG_SIZ];
8146
8147     if (appData.debugMode)
8148       fprintf(debugFP, "Parsing game history: %s\n", game);
8149
8150     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8151     gameInfo.site = StrSave(appData.icsHost);
8152     gameInfo.date = PGNDate();
8153     gameInfo.round = StrSave("-");
8154
8155     /* Parse out names of players */
8156     while (*game == ' ') game++;
8157     p = buf;
8158     while (*game != ' ') *p++ = *game++;
8159     *p = NULLCHAR;
8160     gameInfo.white = StrSave(buf);
8161     while (*game == ' ') game++;
8162     p = buf;
8163     while (*game != ' ' && *game != '\n') *p++ = *game++;
8164     *p = NULLCHAR;
8165     gameInfo.black = StrSave(buf);
8166
8167     /* Parse moves */
8168     boardIndex = blackPlaysFirst ? 1 : 0;
8169     yynewstr(game);
8170     for (;;) {
8171         yyboardindex = boardIndex;
8172         moveType = (ChessMove) yylex();
8173         switch (moveType) {
8174           case IllegalMove:             /* maybe suicide chess, etc. */
8175   if (appData.debugMode) {
8176     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8177     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8178     setbuf(debugFP, NULL);
8179   }
8180           case WhitePromotion:
8181           case BlackPromotion:
8182           case WhiteNonPromotion:
8183           case BlackNonPromotion:
8184           case NormalMove:
8185           case WhiteCapturesEnPassant:
8186           case BlackCapturesEnPassant:
8187           case WhiteKingSideCastle:
8188           case WhiteQueenSideCastle:
8189           case BlackKingSideCastle:
8190           case BlackQueenSideCastle:
8191           case WhiteKingSideCastleWild:
8192           case WhiteQueenSideCastleWild:
8193           case BlackKingSideCastleWild:
8194           case BlackQueenSideCastleWild:
8195           /* PUSH Fabien */
8196           case WhiteHSideCastleFR:
8197           case WhiteASideCastleFR:
8198           case BlackHSideCastleFR:
8199           case BlackASideCastleFR:
8200           /* POP Fabien */
8201             fromX = currentMoveString[0] - AAA;
8202             fromY = currentMoveString[1] - ONE;
8203             toX = currentMoveString[2] - AAA;
8204             toY = currentMoveString[3] - ONE;
8205             promoChar = currentMoveString[4];
8206             break;
8207           case WhiteDrop:
8208           case BlackDrop:
8209             fromX = moveType == WhiteDrop ?
8210               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8211             (int) CharToPiece(ToLower(currentMoveString[0]));
8212             fromY = DROP_RANK;
8213             toX = currentMoveString[2] - AAA;
8214             toY = currentMoveString[3] - ONE;
8215             promoChar = NULLCHAR;
8216             break;
8217           case AmbiguousMove:
8218             /* bug? */
8219             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8220   if (appData.debugMode) {
8221     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8222     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8223     setbuf(debugFP, NULL);
8224   }
8225             DisplayError(buf, 0);
8226             return;
8227           case ImpossibleMove:
8228             /* bug? */
8229             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8230   if (appData.debugMode) {
8231     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8232     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8233     setbuf(debugFP, NULL);
8234   }
8235             DisplayError(buf, 0);
8236             return;
8237           case EndOfFile:
8238             if (boardIndex < backwardMostMove) {
8239                 /* Oops, gap.  How did that happen? */
8240                 DisplayError(_("Gap in move list"), 0);
8241                 return;
8242             }
8243             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8244             if (boardIndex > forwardMostMove) {
8245                 forwardMostMove = boardIndex;
8246             }
8247             return;
8248           case ElapsedTime:
8249             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8250                 strcat(parseList[boardIndex-1], " ");
8251                 strcat(parseList[boardIndex-1], yy_text);
8252             }
8253             continue;
8254           case Comment:
8255           case PGNTag:
8256           case NAG:
8257           default:
8258             /* ignore */
8259             continue;
8260           case WhiteWins:
8261           case BlackWins:
8262           case GameIsDrawn:
8263           case GameUnfinished:
8264             if (gameMode == IcsExamining) {
8265                 if (boardIndex < backwardMostMove) {
8266                     /* Oops, gap.  How did that happen? */
8267                     return;
8268                 }
8269                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8270                 return;
8271             }
8272             gameInfo.result = moveType;
8273             p = strchr(yy_text, '{');
8274             if (p == NULL) p = strchr(yy_text, '(');
8275             if (p == NULL) {
8276                 p = yy_text;
8277                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8278             } else {
8279                 q = strchr(p, *p == '{' ? '}' : ')');
8280                 if (q != NULL) *q = NULLCHAR;
8281                 p++;
8282             }
8283             gameInfo.resultDetails = StrSave(p);
8284             continue;
8285         }
8286         if (boardIndex >= forwardMostMove &&
8287             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8288             backwardMostMove = blackPlaysFirst ? 1 : 0;
8289             return;
8290         }
8291         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8292                                  fromY, fromX, toY, toX, promoChar,
8293                                  parseList[boardIndex]);
8294         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8295         /* currentMoveString is set as a side-effect of yylex */
8296         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8297         strcat(moveList[boardIndex], "\n");
8298         boardIndex++;
8299         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8300         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8301           case MT_NONE:
8302           case MT_STALEMATE:
8303           default:
8304             break;
8305           case MT_CHECK:
8306             if(gameInfo.variant != VariantShogi)
8307                 strcat(parseList[boardIndex - 1], "+");
8308             break;
8309           case MT_CHECKMATE:
8310           case MT_STAINMATE:
8311             strcat(parseList[boardIndex - 1], "#");
8312             break;
8313         }
8314     }
8315 }
8316
8317
8318 /* Apply a move to the given board  */
8319 void
8320 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8321      int fromX, fromY, toX, toY;
8322      int promoChar;
8323      Board board;
8324 {
8325   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8326   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8327
8328     /* [HGM] compute & store e.p. status and castling rights for new position */
8329     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8330
8331       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8332       oldEP = (signed char)board[EP_STATUS];
8333       board[EP_STATUS] = EP_NONE;
8334
8335       if( board[toY][toX] != EmptySquare )
8336            board[EP_STATUS] = EP_CAPTURE;
8337
8338   /* [HGM] In Shatranj and Courier all promotions are to Ferz */
8339   if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
8340        && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
8341
8342   if (fromY == DROP_RANK) {
8343         /* must be first */
8344         piece = board[toY][toX] = (ChessSquare) fromX;
8345   } else {
8346       int i;
8347
8348       if( board[fromY][fromX] == WhitePawn ) {
8349            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8350                board[EP_STATUS] = EP_PAWN_MOVE;
8351            if( toY-fromY==2) {
8352                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8353                         gameInfo.variant != VariantBerolina || toX < fromX)
8354                       board[EP_STATUS] = toX | berolina;
8355                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8356                         gameInfo.variant != VariantBerolina || toX > fromX)
8357                       board[EP_STATUS] = toX;
8358            }
8359       } else
8360       if( board[fromY][fromX] == BlackPawn ) {
8361            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8362                board[EP_STATUS] = EP_PAWN_MOVE;
8363            if( toY-fromY== -2) {
8364                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8365                         gameInfo.variant != VariantBerolina || toX < fromX)
8366                       board[EP_STATUS] = toX | berolina;
8367                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8368                         gameInfo.variant != VariantBerolina || toX > fromX)
8369                       board[EP_STATUS] = toX;
8370            }
8371        }
8372
8373        for(i=0; i<nrCastlingRights; i++) {
8374            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8375               board[CASTLING][i] == toX   && castlingRank[i] == toY
8376              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8377        }
8378
8379      if (fromX == toX && fromY == toY) return;
8380
8381      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8382      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8383      if(gameInfo.variant == VariantKnightmate)
8384          king += (int) WhiteUnicorn - (int) WhiteKing;
8385
8386     /* Code added by Tord: */
8387     /* FRC castling assumed when king captures friendly rook. */
8388     if (board[fromY][fromX] == WhiteKing &&
8389              board[toY][toX] == WhiteRook) {
8390       board[fromY][fromX] = EmptySquare;
8391       board[toY][toX] = EmptySquare;
8392       if(toX > fromX) {
8393         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8394       } else {
8395         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8396       }
8397     } else if (board[fromY][fromX] == BlackKing &&
8398                board[toY][toX] == BlackRook) {
8399       board[fromY][fromX] = EmptySquare;
8400       board[toY][toX] = EmptySquare;
8401       if(toX > fromX) {
8402         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8403       } else {
8404         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8405       }
8406     /* End of code added by Tord */
8407
8408     } else if (board[fromY][fromX] == king
8409         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8410         && toY == fromY && toX > fromX+1) {
8411         board[fromY][fromX] = EmptySquare;
8412         board[toY][toX] = king;
8413         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8414         board[fromY][BOARD_RGHT-1] = EmptySquare;
8415     } else if (board[fromY][fromX] == king
8416         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8417                && toY == fromY && toX < fromX-1) {
8418         board[fromY][fromX] = EmptySquare;
8419         board[toY][toX] = king;
8420         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8421         board[fromY][BOARD_LEFT] = EmptySquare;
8422     } else if (board[fromY][fromX] == WhitePawn
8423                && toY >= BOARD_HEIGHT-promoRank
8424                && gameInfo.variant != VariantXiangqi
8425                ) {
8426         /* white pawn promotion */
8427         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8428         if (board[toY][toX] == EmptySquare) {
8429             board[toY][toX] = WhiteQueen;
8430         }
8431         if(gameInfo.variant==VariantBughouse ||
8432            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8433             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8434         board[fromY][fromX] = EmptySquare;
8435     } else if ((fromY == BOARD_HEIGHT-4)
8436                && (toX != fromX)
8437                && gameInfo.variant != VariantXiangqi
8438                && gameInfo.variant != VariantBerolina
8439                && (board[fromY][fromX] == WhitePawn)
8440                && (board[toY][toX] == EmptySquare)) {
8441         board[fromY][fromX] = EmptySquare;
8442         board[toY][toX] = WhitePawn;
8443         captured = board[toY - 1][toX];
8444         board[toY - 1][toX] = EmptySquare;
8445     } else if ((fromY == BOARD_HEIGHT-4)
8446                && (toX == fromX)
8447                && gameInfo.variant == VariantBerolina
8448                && (board[fromY][fromX] == WhitePawn)
8449                && (board[toY][toX] == EmptySquare)) {
8450         board[fromY][fromX] = EmptySquare;
8451         board[toY][toX] = WhitePawn;
8452         if(oldEP & EP_BEROLIN_A) {
8453                 captured = board[fromY][fromX-1];
8454                 board[fromY][fromX-1] = EmptySquare;
8455         }else{  captured = board[fromY][fromX+1];
8456                 board[fromY][fromX+1] = EmptySquare;
8457         }
8458     } else if (board[fromY][fromX] == king
8459         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8460                && toY == fromY && toX > fromX+1) {
8461         board[fromY][fromX] = EmptySquare;
8462         board[toY][toX] = king;
8463         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8464         board[fromY][BOARD_RGHT-1] = EmptySquare;
8465     } else if (board[fromY][fromX] == king
8466         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8467                && toY == fromY && toX < fromX-1) {
8468         board[fromY][fromX] = EmptySquare;
8469         board[toY][toX] = king;
8470         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8471         board[fromY][BOARD_LEFT] = EmptySquare;
8472     } else if (fromY == 7 && fromX == 3
8473                && board[fromY][fromX] == BlackKing
8474                && toY == 7 && toX == 5) {
8475         board[fromY][fromX] = EmptySquare;
8476         board[toY][toX] = BlackKing;
8477         board[fromY][7] = EmptySquare;
8478         board[toY][4] = BlackRook;
8479     } else if (fromY == 7 && fromX == 3
8480                && board[fromY][fromX] == BlackKing
8481                && toY == 7 && toX == 1) {
8482         board[fromY][fromX] = EmptySquare;
8483         board[toY][toX] = BlackKing;
8484         board[fromY][0] = EmptySquare;
8485         board[toY][2] = BlackRook;
8486     } else if (board[fromY][fromX] == BlackPawn
8487                && toY < promoRank
8488                && gameInfo.variant != VariantXiangqi
8489                ) {
8490         /* black pawn promotion */
8491         board[toY][toX] = CharToPiece(ToLower(promoChar));
8492         if (board[toY][toX] == EmptySquare) {
8493             board[toY][toX] = BlackQueen;
8494         }
8495         if(gameInfo.variant==VariantBughouse ||
8496            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8497             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8498         board[fromY][fromX] = EmptySquare;
8499     } else if ((fromY == 3)
8500                && (toX != fromX)
8501                && gameInfo.variant != VariantXiangqi
8502                && gameInfo.variant != VariantBerolina
8503                && (board[fromY][fromX] == BlackPawn)
8504                && (board[toY][toX] == EmptySquare)) {
8505         board[fromY][fromX] = EmptySquare;
8506         board[toY][toX] = BlackPawn;
8507         captured = board[toY + 1][toX];
8508         board[toY + 1][toX] = EmptySquare;
8509     } else if ((fromY == 3)
8510                && (toX == fromX)
8511                && gameInfo.variant == VariantBerolina
8512                && (board[fromY][fromX] == BlackPawn)
8513                && (board[toY][toX] == EmptySquare)) {
8514         board[fromY][fromX] = EmptySquare;
8515         board[toY][toX] = BlackPawn;
8516         if(oldEP & EP_BEROLIN_A) {
8517                 captured = board[fromY][fromX-1];
8518                 board[fromY][fromX-1] = EmptySquare;
8519         }else{  captured = board[fromY][fromX+1];
8520                 board[fromY][fromX+1] = EmptySquare;
8521         }
8522     } else {
8523         board[toY][toX] = board[fromY][fromX];
8524         board[fromY][fromX] = EmptySquare;
8525     }
8526   }
8527
8528     if (gameInfo.holdingsWidth != 0) {
8529
8530       /* !!A lot more code needs to be written to support holdings  */
8531       /* [HGM] OK, so I have written it. Holdings are stored in the */
8532       /* penultimate board files, so they are automaticlly stored   */
8533       /* in the game history.                                       */
8534       if (fromY == DROP_RANK) {
8535         /* Delete from holdings, by decreasing count */
8536         /* and erasing image if necessary            */
8537         p = (int) fromX;
8538         if(p < (int) BlackPawn) { /* white drop */
8539              p -= (int)WhitePawn;
8540                  p = PieceToNumber((ChessSquare)p);
8541              if(p >= gameInfo.holdingsSize) p = 0;
8542              if(--board[p][BOARD_WIDTH-2] <= 0)
8543                   board[p][BOARD_WIDTH-1] = EmptySquare;
8544              if((int)board[p][BOARD_WIDTH-2] < 0)
8545                         board[p][BOARD_WIDTH-2] = 0;
8546         } else {                  /* black drop */
8547              p -= (int)BlackPawn;
8548                  p = PieceToNumber((ChessSquare)p);
8549              if(p >= gameInfo.holdingsSize) p = 0;
8550              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8551                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8552              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8553                         board[BOARD_HEIGHT-1-p][1] = 0;
8554         }
8555       }
8556       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8557           && gameInfo.variant != VariantBughouse        ) {
8558         /* [HGM] holdings: Add to holdings, if holdings exist */
8559         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8560                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8561                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8562         }
8563         p = (int) captured;
8564         if (p >= (int) BlackPawn) {
8565           p -= (int)BlackPawn;
8566           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8567                   /* in Shogi restore piece to its original  first */
8568                   captured = (ChessSquare) (DEMOTED captured);
8569                   p = DEMOTED p;
8570           }
8571           p = PieceToNumber((ChessSquare)p);
8572           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8573           board[p][BOARD_WIDTH-2]++;
8574           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8575         } else {
8576           p -= (int)WhitePawn;
8577           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8578                   captured = (ChessSquare) (DEMOTED captured);
8579                   p = DEMOTED p;
8580           }
8581           p = PieceToNumber((ChessSquare)p);
8582           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8583           board[BOARD_HEIGHT-1-p][1]++;
8584           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8585         }
8586       }
8587     } else if (gameInfo.variant == VariantAtomic) {
8588       if (captured != EmptySquare) {
8589         int y, x;
8590         for (y = toY-1; y <= toY+1; y++) {
8591           for (x = toX-1; x <= toX+1; x++) {
8592             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8593                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8594               board[y][x] = EmptySquare;
8595             }
8596           }
8597         }
8598         board[toY][toX] = EmptySquare;
8599       }
8600     }
8601     if(promoChar == '+') {
8602         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8603         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8604     } else if(!appData.testLegality) { // without legality testing, unconditionally believe promoChar
8605         board[toY][toX] = CharToPiece(promoChar);
8606     }
8607     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
8608                 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
8609         // [HGM] superchess: take promotion piece out of holdings
8610         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8611         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8612             if(!--board[k][BOARD_WIDTH-2])
8613                 board[k][BOARD_WIDTH-1] = EmptySquare;
8614         } else {
8615             if(!--board[BOARD_HEIGHT-1-k][1])
8616                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8617         }
8618     }
8619
8620 }
8621
8622 /* Updates forwardMostMove */
8623 void
8624 MakeMove(fromX, fromY, toX, toY, promoChar)
8625      int fromX, fromY, toX, toY;
8626      int promoChar;
8627 {
8628 //    forwardMostMove++; // [HGM] bare: moved downstream
8629
8630     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8631         int timeLeft; static int lastLoadFlag=0; int king, piece;
8632         piece = boards[forwardMostMove][fromY][fromX];
8633         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8634         if(gameInfo.variant == VariantKnightmate)
8635             king += (int) WhiteUnicorn - (int) WhiteKing;
8636         if(forwardMostMove == 0) {
8637             if(blackPlaysFirst)
8638                 fprintf(serverMoves, "%s;", second.tidy);
8639             fprintf(serverMoves, "%s;", first.tidy);
8640             if(!blackPlaysFirst)
8641                 fprintf(serverMoves, "%s;", second.tidy);
8642         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8643         lastLoadFlag = loadFlag;
8644         // print base move
8645         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8646         // print castling suffix
8647         if( toY == fromY && piece == king ) {
8648             if(toX-fromX > 1)
8649                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8650             if(fromX-toX >1)
8651                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8652         }
8653         // e.p. suffix
8654         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8655              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8656              boards[forwardMostMove][toY][toX] == EmptySquare
8657              && fromX != toX && fromY != toY)
8658                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8659         // promotion suffix
8660         if(promoChar != NULLCHAR)
8661                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8662         if(!loadFlag) {
8663             fprintf(serverMoves, "/%d/%d",
8664                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8665             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8666             else                      timeLeft = blackTimeRemaining/1000;
8667             fprintf(serverMoves, "/%d", timeLeft);
8668         }
8669         fflush(serverMoves);
8670     }
8671
8672     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8673       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8674                         0, 1);
8675       return;
8676     }
8677     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8678     if (commentList[forwardMostMove+1] != NULL) {
8679         free(commentList[forwardMostMove+1]);
8680         commentList[forwardMostMove+1] = NULL;
8681     }
8682     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8683     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8684     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8685     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8686     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8687     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8688     gameInfo.result = GameUnfinished;
8689     if (gameInfo.resultDetails != NULL) {
8690         free(gameInfo.resultDetails);
8691         gameInfo.resultDetails = NULL;
8692     }
8693     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
8694                               moveList[forwardMostMove - 1]);
8695     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
8696                              PosFlags(forwardMostMove - 1),
8697                              fromY, fromX, toY, toX, promoChar,
8698                              parseList[forwardMostMove - 1]);
8699     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
8700       case MT_NONE:
8701       case MT_STALEMATE:
8702       default:
8703         break;
8704       case MT_CHECK:
8705         if(gameInfo.variant != VariantShogi)
8706             strcat(parseList[forwardMostMove - 1], "+");
8707         break;
8708       case MT_CHECKMATE:
8709       case MT_STAINMATE:
8710         strcat(parseList[forwardMostMove - 1], "#");
8711         break;
8712     }
8713     if (appData.debugMode) {
8714         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
8715     }
8716
8717 }
8718
8719 /* Updates currentMove if not pausing */
8720 void
8721 ShowMove(fromX, fromY, toX, toY)
8722 {
8723     int instant = (gameMode == PlayFromGameFile) ?
8724         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
8725     if(appData.noGUI) return;
8726     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
8727         if (!instant) {
8728             if (forwardMostMove == currentMove + 1) {
8729                 AnimateMove(boards[forwardMostMove - 1],
8730                             fromX, fromY, toX, toY);
8731             }
8732             if (appData.highlightLastMove) {
8733                 SetHighlights(fromX, fromY, toX, toY);
8734             }
8735         }
8736         currentMove = forwardMostMove;
8737     }
8738
8739     if (instant) return;
8740
8741     DisplayMove(currentMove - 1);
8742     DrawPosition(FALSE, boards[currentMove]);
8743     DisplayBothClocks();
8744     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
8745 }
8746
8747 void SendEgtPath(ChessProgramState *cps)
8748 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
8749         char buf[MSG_SIZ], name[MSG_SIZ], *p;
8750
8751         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
8752
8753         while(*p) {
8754             char c, *q = name+1, *r, *s;
8755
8756             name[0] = ','; // extract next format name from feature and copy with prefixed ','
8757             while(*p && *p != ',') *q++ = *p++;
8758             *q++ = ':'; *q = 0;
8759             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
8760                 strcmp(name, ",nalimov:") == 0 ) {
8761                 // take nalimov path from the menu-changeable option first, if it is defined
8762               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
8763                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
8764             } else
8765             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
8766                 (s = StrStr(appData.egtFormats, name)) != NULL) {
8767                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
8768                 s = r = StrStr(s, ":") + 1; // beginning of path info
8769                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
8770                 c = *r; *r = 0;             // temporarily null-terminate path info
8771                     *--q = 0;               // strip of trailig ':' from name
8772                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
8773                 *r = c;
8774                 SendToProgram(buf,cps);     // send egtbpath command for this format
8775             }
8776             if(*p == ',') p++; // read away comma to position for next format name
8777         }
8778 }
8779
8780 void
8781 InitChessProgram(cps, setup)
8782      ChessProgramState *cps;
8783      int setup; /* [HGM] needed to setup FRC opening position */
8784 {
8785     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
8786     if (appData.noChessProgram) return;
8787     hintRequested = FALSE;
8788     bookRequested = FALSE;
8789
8790     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
8791     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
8792     if(cps->memSize) { /* [HGM] memory */
8793       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
8794         SendToProgram(buf, cps);
8795     }
8796     SendEgtPath(cps); /* [HGM] EGT */
8797     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
8798       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
8799         SendToProgram(buf, cps);
8800     }
8801
8802     SendToProgram(cps->initString, cps);
8803     if (gameInfo.variant != VariantNormal &&
8804         gameInfo.variant != VariantLoadable
8805         /* [HGM] also send variant if board size non-standard */
8806         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
8807                                             ) {
8808       char *v = VariantName(gameInfo.variant);
8809       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
8810         /* [HGM] in protocol 1 we have to assume all variants valid */
8811         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
8812         DisplayFatalError(buf, 0, 1);
8813         return;
8814       }
8815
8816       /* [HGM] make prefix for non-standard board size. Awkward testing... */
8817       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8818       if( gameInfo.variant == VariantXiangqi )
8819            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
8820       if( gameInfo.variant == VariantShogi )
8821            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
8822       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
8823            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
8824       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
8825                                gameInfo.variant == VariantGothic  || gameInfo.variant == VariantFalcon )
8826            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8827       if( gameInfo.variant == VariantCourier )
8828            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
8829       if( gameInfo.variant == VariantSuper )
8830            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8831       if( gameInfo.variant == VariantGreat )
8832            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
8833
8834       if(overruled) {
8835         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
8836                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
8837            /* [HGM] varsize: try first if this defiant size variant is specifically known */
8838            if(StrStr(cps->variants, b) == NULL) {
8839                // specific sized variant not known, check if general sizing allowed
8840                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
8841                    if(StrStr(cps->variants, "boardsize") == NULL) {
8842                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
8843                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
8844                        DisplayFatalError(buf, 0, 1);
8845                        return;
8846                    }
8847                    /* [HGM] here we really should compare with the maximum supported board size */
8848                }
8849            }
8850       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
8851       snprintf(buf, MSG_SIZ, "variant %s\n", b);
8852       SendToProgram(buf, cps);
8853     }
8854     currentlyInitializedVariant = gameInfo.variant;
8855
8856     /* [HGM] send opening position in FRC to first engine */
8857     if(setup) {
8858           SendToProgram("force\n", cps);
8859           SendBoard(cps, 0);
8860           /* engine is now in force mode! Set flag to wake it up after first move. */
8861           setboardSpoiledMachineBlack = 1;
8862     }
8863
8864     if (cps->sendICS) {
8865       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
8866       SendToProgram(buf, cps);
8867     }
8868     cps->maybeThinking = FALSE;
8869     cps->offeredDraw = 0;
8870     if (!appData.icsActive) {
8871         SendTimeControl(cps, movesPerSession, timeControl,
8872                         timeIncrement, appData.searchDepth,
8873                         searchTime);
8874     }
8875     if (appData.showThinking
8876         // [HGM] thinking: four options require thinking output to be sent
8877         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8878                                 ) {
8879         SendToProgram("post\n", cps);
8880     }
8881     SendToProgram("hard\n", cps);
8882     if (!appData.ponderNextMove) {
8883         /* Warning: "easy" is a toggle in GNU Chess, so don't send
8884            it without being sure what state we are in first.  "hard"
8885            is not a toggle, so that one is OK.
8886          */
8887         SendToProgram("easy\n", cps);
8888     }
8889     if (cps->usePing) {
8890       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
8891       SendToProgram(buf, cps);
8892     }
8893     cps->initDone = TRUE;
8894 }
8895
8896
8897 void
8898 StartChessProgram(cps)
8899      ChessProgramState *cps;
8900 {
8901     char buf[MSG_SIZ];
8902     int err;
8903
8904     if (appData.noChessProgram) return;
8905     cps->initDone = FALSE;
8906
8907     if (strcmp(cps->host, "localhost") == 0) {
8908         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
8909     } else if (*appData.remoteShell == NULLCHAR) {
8910         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
8911     } else {
8912         if (*appData.remoteUser == NULLCHAR) {
8913           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
8914                     cps->program);
8915         } else {
8916           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
8917                     cps->host, appData.remoteUser, cps->program);
8918         }
8919         err = StartChildProcess(buf, "", &cps->pr);
8920     }
8921
8922     if (err != 0) {
8923       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
8924         DisplayFatalError(buf, err, 1);
8925         cps->pr = NoProc;
8926         cps->isr = NULL;
8927         return;
8928     }
8929
8930     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
8931     if (cps->protocolVersion > 1) {
8932       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
8933       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
8934       cps->comboCnt = 0;  //                and values of combo boxes
8935       SendToProgram(buf, cps);
8936     } else {
8937       SendToProgram("xboard\n", cps);
8938     }
8939 }
8940
8941
8942 void
8943 TwoMachinesEventIfReady P((void))
8944 {
8945   if (first.lastPing != first.lastPong) {
8946     DisplayMessage("", _("Waiting for first chess program"));
8947     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8948     return;
8949   }
8950   if (second.lastPing != second.lastPong) {
8951     DisplayMessage("", _("Waiting for second chess program"));
8952     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
8953     return;
8954   }
8955   ThawUI();
8956   TwoMachinesEvent();
8957 }
8958
8959 void
8960 NextMatchGame P((void))
8961 {
8962     int index; /* [HGM] autoinc: step load index during match */
8963     Reset(FALSE, TRUE);
8964     if (*appData.loadGameFile != NULLCHAR) {
8965         index = appData.loadGameIndex;
8966         if(index < 0) { // [HGM] autoinc
8967             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8968             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8969         }
8970         LoadGameFromFile(appData.loadGameFile,
8971                          index,
8972                          appData.loadGameFile, FALSE);
8973     } else if (*appData.loadPositionFile != NULLCHAR) {
8974         index = appData.loadPositionIndex;
8975         if(index < 0) { // [HGM] autoinc
8976             lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
8977             if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
8978         }
8979         LoadPositionFromFile(appData.loadPositionFile,
8980                              index,
8981                              appData.loadPositionFile);
8982     }
8983     TwoMachinesEventIfReady();
8984 }
8985
8986 void UserAdjudicationEvent( int result )
8987 {
8988     ChessMove gameResult = GameIsDrawn;
8989
8990     if( result > 0 ) {
8991         gameResult = WhiteWins;
8992     }
8993     else if( result < 0 ) {
8994         gameResult = BlackWins;
8995     }
8996
8997     if( gameMode == TwoMachinesPlay ) {
8998         GameEnds( gameResult, "User adjudication", GE_XBOARD );
8999     }
9000 }
9001
9002
9003 // [HGM] save: calculate checksum of game to make games easily identifiable
9004 int StringCheckSum(char *s)
9005 {
9006         int i = 0;
9007         if(s==NULL) return 0;
9008         while(*s) i = i*259 + *s++;
9009         return i;
9010 }
9011
9012 int GameCheckSum()
9013 {
9014         int i, sum=0;
9015         for(i=backwardMostMove; i<forwardMostMove; i++) {
9016                 sum += pvInfoList[i].depth;
9017                 sum += StringCheckSum(parseList[i]);
9018                 sum += StringCheckSum(commentList[i]);
9019                 sum *= 261;
9020         }
9021         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9022         return sum + StringCheckSum(commentList[i]);
9023 } // end of save patch
9024
9025 void
9026 GameEnds(result, resultDetails, whosays)
9027      ChessMove result;
9028      char *resultDetails;
9029      int whosays;
9030 {
9031     GameMode nextGameMode;
9032     int isIcsGame;
9033     char buf[MSG_SIZ], popupRequested = 0;
9034
9035     if(endingGame) return; /* [HGM] crash: forbid recursion */
9036     endingGame = 1;
9037     if(twoBoards) { // [HGM] dual: switch back to one board
9038         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9039         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9040     }
9041     if (appData.debugMode) {
9042       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9043               result, resultDetails ? resultDetails : "(null)", whosays);
9044     }
9045
9046     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9047
9048     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9049         /* If we are playing on ICS, the server decides when the
9050            game is over, but the engine can offer to draw, claim
9051            a draw, or resign.
9052          */
9053 #if ZIPPY
9054         if (appData.zippyPlay && first.initDone) {
9055             if (result == GameIsDrawn) {
9056                 /* In case draw still needs to be claimed */
9057                 SendToICS(ics_prefix);
9058                 SendToICS("draw\n");
9059             } else if (StrCaseStr(resultDetails, "resign")) {
9060                 SendToICS(ics_prefix);
9061                 SendToICS("resign\n");
9062             }
9063         }
9064 #endif
9065         endingGame = 0; /* [HGM] crash */
9066         return;
9067     }
9068
9069     /* If we're loading the game from a file, stop */
9070     if (whosays == GE_FILE) {
9071       (void) StopLoadGameTimer();
9072       gameFileFP = NULL;
9073     }
9074
9075     /* Cancel draw offers */
9076     first.offeredDraw = second.offeredDraw = 0;
9077
9078     /* If this is an ICS game, only ICS can really say it's done;
9079        if not, anyone can. */
9080     isIcsGame = (gameMode == IcsPlayingWhite ||
9081                  gameMode == IcsPlayingBlack ||
9082                  gameMode == IcsObserving    ||
9083                  gameMode == IcsExamining);
9084
9085     if (!isIcsGame || whosays == GE_ICS) {
9086         /* OK -- not an ICS game, or ICS said it was done */
9087         StopClocks();
9088         if (!isIcsGame && !appData.noChessProgram)
9089           SetUserThinkingEnables();
9090
9091         /* [HGM] if a machine claims the game end we verify this claim */
9092         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9093             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9094                 char claimer;
9095                 ChessMove trueResult = (ChessMove) -1;
9096
9097                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9098                                             first.twoMachinesColor[0] :
9099                                             second.twoMachinesColor[0] ;
9100
9101                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9102                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9103                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9104                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9105                 } else
9106                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9107                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9108                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9109                 } else
9110                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9111                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9112                 }
9113
9114                 // now verify win claims, but not in drop games, as we don't understand those yet
9115                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9116                                                  || gameInfo.variant == VariantGreat) &&
9117                     (result == WhiteWins && claimer == 'w' ||
9118                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9119                       if (appData.debugMode) {
9120                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9121                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9122                       }
9123                       if(result != trueResult) {
9124                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9125                               result = claimer == 'w' ? BlackWins : WhiteWins;
9126                               resultDetails = buf;
9127                       }
9128                 } else
9129                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9130                     && (forwardMostMove <= backwardMostMove ||
9131                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9132                         (claimer=='b')==(forwardMostMove&1))
9133                                                                                   ) {
9134                       /* [HGM] verify: draws that were not flagged are false claims */
9135                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9136                       result = claimer == 'w' ? BlackWins : WhiteWins;
9137                       resultDetails = buf;
9138                 }
9139                 /* (Claiming a loss is accepted no questions asked!) */
9140             }
9141             /* [HGM] bare: don't allow bare King to win */
9142             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9143                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9144                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9145                && result != GameIsDrawn)
9146             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9147                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9148                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9149                         if(p >= 0 && p <= (int)WhiteKing) k++;
9150                 }
9151                 if (appData.debugMode) {
9152                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9153                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9154                 }
9155                 if(k <= 1) {
9156                         result = GameIsDrawn;
9157                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9158                         resultDetails = buf;
9159                 }
9160             }
9161         }
9162
9163
9164         if(serverMoves != NULL && !loadFlag) { char c = '=';
9165             if(result==WhiteWins) c = '+';
9166             if(result==BlackWins) c = '-';
9167             if(resultDetails != NULL)
9168                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9169         }
9170         if (resultDetails != NULL) {
9171             gameInfo.result = result;
9172             gameInfo.resultDetails = StrSave(resultDetails);
9173
9174             /* display last move only if game was not loaded from file */
9175             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9176                 DisplayMove(currentMove - 1);
9177
9178             if (forwardMostMove != 0) {
9179                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9180                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9181                                                                 ) {
9182                     if (*appData.saveGameFile != NULLCHAR) {
9183                         SaveGameToFile(appData.saveGameFile, TRUE);
9184                     } else if (appData.autoSaveGames) {
9185                         AutoSaveGame();
9186                     }
9187                     if (*appData.savePositionFile != NULLCHAR) {
9188                         SavePositionToFile(appData.savePositionFile);
9189                     }
9190                 }
9191             }
9192
9193             /* Tell program how game ended in case it is learning */
9194             /* [HGM] Moved this to after saving the PGN, just in case */
9195             /* engine died and we got here through time loss. In that */
9196             /* case we will get a fatal error writing the pipe, which */
9197             /* would otherwise lose us the PGN.                       */
9198             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9199             /* output during GameEnds should never be fatal anymore   */
9200             if (gameMode == MachinePlaysWhite ||
9201                 gameMode == MachinePlaysBlack ||
9202                 gameMode == TwoMachinesPlay ||
9203                 gameMode == IcsPlayingWhite ||
9204                 gameMode == IcsPlayingBlack ||
9205                 gameMode == BeginningOfGame) {
9206                 char buf[MSG_SIZ];
9207                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9208                         resultDetails);
9209                 if (first.pr != NoProc) {
9210                     SendToProgram(buf, &first);
9211                 }
9212                 if (second.pr != NoProc &&
9213                     gameMode == TwoMachinesPlay) {
9214                     SendToProgram(buf, &second);
9215                 }
9216             }
9217         }
9218
9219         if (appData.icsActive) {
9220             if (appData.quietPlay &&
9221                 (gameMode == IcsPlayingWhite ||
9222                  gameMode == IcsPlayingBlack)) {
9223                 SendToICS(ics_prefix);
9224                 SendToICS("set shout 1\n");
9225             }
9226             nextGameMode = IcsIdle;
9227             ics_user_moved = FALSE;
9228             /* clean up premove.  It's ugly when the game has ended and the
9229              * premove highlights are still on the board.
9230              */
9231             if (gotPremove) {
9232               gotPremove = FALSE;
9233               ClearPremoveHighlights();
9234               DrawPosition(FALSE, boards[currentMove]);
9235             }
9236             if (whosays == GE_ICS) {
9237                 switch (result) {
9238                 case WhiteWins:
9239                     if (gameMode == IcsPlayingWhite)
9240                         PlayIcsWinSound();
9241                     else if(gameMode == IcsPlayingBlack)
9242                         PlayIcsLossSound();
9243                     break;
9244                 case BlackWins:
9245                     if (gameMode == IcsPlayingBlack)
9246                         PlayIcsWinSound();
9247                     else if(gameMode == IcsPlayingWhite)
9248                         PlayIcsLossSound();
9249                     break;
9250                 case GameIsDrawn:
9251                     PlayIcsDrawSound();
9252                     break;
9253                 default:
9254                     PlayIcsUnfinishedSound();
9255                 }
9256             }
9257         } else if (gameMode == EditGame ||
9258                    gameMode == PlayFromGameFile ||
9259                    gameMode == AnalyzeMode ||
9260                    gameMode == AnalyzeFile) {
9261             nextGameMode = gameMode;
9262         } else {
9263             nextGameMode = EndOfGame;
9264         }
9265         pausing = FALSE;
9266         ModeHighlight();
9267     } else {
9268         nextGameMode = gameMode;
9269     }
9270
9271     if (appData.noChessProgram) {
9272         gameMode = nextGameMode;
9273         ModeHighlight();
9274         endingGame = 0; /* [HGM] crash */
9275         return;
9276     }
9277
9278     if (first.reuse) {
9279         /* Put first chess program into idle state */
9280         if (first.pr != NoProc &&
9281             (gameMode == MachinePlaysWhite ||
9282              gameMode == MachinePlaysBlack ||
9283              gameMode == TwoMachinesPlay ||
9284              gameMode == IcsPlayingWhite ||
9285              gameMode == IcsPlayingBlack ||
9286              gameMode == BeginningOfGame)) {
9287             SendToProgram("force\n", &first);
9288             if (first.usePing) {
9289               char buf[MSG_SIZ];
9290               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9291               SendToProgram(buf, &first);
9292             }
9293         }
9294     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9295         /* Kill off first chess program */
9296         if (first.isr != NULL)
9297           RemoveInputSource(first.isr);
9298         first.isr = NULL;
9299
9300         if (first.pr != NoProc) {
9301             ExitAnalyzeMode();
9302             DoSleep( appData.delayBeforeQuit );
9303             SendToProgram("quit\n", &first);
9304             DoSleep( appData.delayAfterQuit );
9305             DestroyChildProcess(first.pr, first.useSigterm);
9306         }
9307         first.pr = NoProc;
9308     }
9309     if (second.reuse) {
9310         /* Put second chess program into idle state */
9311         if (second.pr != NoProc &&
9312             gameMode == TwoMachinesPlay) {
9313             SendToProgram("force\n", &second);
9314             if (second.usePing) {
9315               char buf[MSG_SIZ];
9316               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9317               SendToProgram(buf, &second);
9318             }
9319         }
9320     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9321         /* Kill off second chess program */
9322         if (second.isr != NULL)
9323           RemoveInputSource(second.isr);
9324         second.isr = NULL;
9325
9326         if (second.pr != NoProc) {
9327             DoSleep( appData.delayBeforeQuit );
9328             SendToProgram("quit\n", &second);
9329             DoSleep( appData.delayAfterQuit );
9330             DestroyChildProcess(second.pr, second.useSigterm);
9331         }
9332         second.pr = NoProc;
9333     }
9334
9335     if (matchMode && gameMode == TwoMachinesPlay) {
9336         switch (result) {
9337         case WhiteWins:
9338           if (first.twoMachinesColor[0] == 'w') {
9339             first.matchWins++;
9340           } else {
9341             second.matchWins++;
9342           }
9343           break;
9344         case BlackWins:
9345           if (first.twoMachinesColor[0] == 'b') {
9346             first.matchWins++;
9347           } else {
9348             second.matchWins++;
9349           }
9350           break;
9351         default:
9352           break;
9353         }
9354         if (matchGame < appData.matchGames) {
9355             char *tmp;
9356             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9357                 tmp = first.twoMachinesColor;
9358                 first.twoMachinesColor = second.twoMachinesColor;
9359                 second.twoMachinesColor = tmp;
9360             }
9361             gameMode = nextGameMode;
9362             matchGame++;
9363             if(appData.matchPause>10000 || appData.matchPause<10)
9364                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9365             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9366             endingGame = 0; /* [HGM] crash */
9367             return;
9368         } else {
9369             gameMode = nextGameMode;
9370             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9371                      first.tidy, second.tidy,
9372                      first.matchWins, second.matchWins,
9373                      appData.matchGames - (first.matchWins + second.matchWins));
9374             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9375         }
9376     }
9377     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9378         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9379       ExitAnalyzeMode();
9380     gameMode = nextGameMode;
9381     ModeHighlight();
9382     endingGame = 0;  /* [HGM] crash */
9383     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9384       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9385         matchMode = FALSE; appData.matchGames = matchGame = 0;
9386         DisplayNote(buf);
9387       }
9388     }
9389 }
9390
9391 /* Assumes program was just initialized (initString sent).
9392    Leaves program in force mode. */
9393 void
9394 FeedMovesToProgram(cps, upto)
9395      ChessProgramState *cps;
9396      int upto;
9397 {
9398     int i;
9399
9400     if (appData.debugMode)
9401       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9402               startedFromSetupPosition ? "position and " : "",
9403               backwardMostMove, upto, cps->which);
9404     if(currentlyInitializedVariant != gameInfo.variant) {
9405       char buf[MSG_SIZ];
9406         // [HGM] variantswitch: make engine aware of new variant
9407         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9408                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9409         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9410         SendToProgram(buf, cps);
9411         currentlyInitializedVariant = gameInfo.variant;
9412     }
9413     SendToProgram("force\n", cps);
9414     if (startedFromSetupPosition) {
9415         SendBoard(cps, backwardMostMove);
9416     if (appData.debugMode) {
9417         fprintf(debugFP, "feedMoves\n");
9418     }
9419     }
9420     for (i = backwardMostMove; i < upto; i++) {
9421         SendMoveToProgram(i, cps);
9422     }
9423 }
9424
9425
9426 void
9427 ResurrectChessProgram()
9428 {
9429      /* The chess program may have exited.
9430         If so, restart it and feed it all the moves made so far. */
9431
9432     if (appData.noChessProgram || first.pr != NoProc) return;
9433
9434     StartChessProgram(&first);
9435     InitChessProgram(&first, FALSE);
9436     FeedMovesToProgram(&first, currentMove);
9437
9438     if (!first.sendTime) {
9439         /* can't tell gnuchess what its clock should read,
9440            so we bow to its notion. */
9441         ResetClocks();
9442         timeRemaining[0][currentMove] = whiteTimeRemaining;
9443         timeRemaining[1][currentMove] = blackTimeRemaining;
9444     }
9445
9446     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9447                 appData.icsEngineAnalyze) && first.analysisSupport) {
9448       SendToProgram("analyze\n", &first);
9449       first.analyzing = TRUE;
9450     }
9451 }
9452
9453 /*
9454  * Button procedures
9455  */
9456 void
9457 Reset(redraw, init)
9458      int redraw, init;
9459 {
9460     int i;
9461
9462     if (appData.debugMode) {
9463         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9464                 redraw, init, gameMode);
9465     }
9466     CleanupTail(); // [HGM] vari: delete any stored variations
9467     pausing = pauseExamInvalid = FALSE;
9468     startedFromSetupPosition = blackPlaysFirst = FALSE;
9469     firstMove = TRUE;
9470     whiteFlag = blackFlag = FALSE;
9471     userOfferedDraw = FALSE;
9472     hintRequested = bookRequested = FALSE;
9473     first.maybeThinking = FALSE;
9474     second.maybeThinking = FALSE;
9475     first.bookSuspend = FALSE; // [HGM] book
9476     second.bookSuspend = FALSE;
9477     thinkOutput[0] = NULLCHAR;
9478     lastHint[0] = NULLCHAR;
9479     ClearGameInfo(&gameInfo);
9480     gameInfo.variant = StringToVariant(appData.variant);
9481     ics_user_moved = ics_clock_paused = FALSE;
9482     ics_getting_history = H_FALSE;
9483     ics_gamenum = -1;
9484     white_holding[0] = black_holding[0] = NULLCHAR;
9485     ClearProgramStats();
9486     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9487
9488     ResetFrontEnd();
9489     ClearHighlights();
9490     flipView = appData.flipView;
9491     ClearPremoveHighlights();
9492     gotPremove = FALSE;
9493     alarmSounded = FALSE;
9494
9495     GameEnds(EndOfFile, NULL, GE_PLAYER);
9496     if(appData.serverMovesName != NULL) {
9497         /* [HGM] prepare to make moves file for broadcasting */
9498         clock_t t = clock();
9499         if(serverMoves != NULL) fclose(serverMoves);
9500         serverMoves = fopen(appData.serverMovesName, "r");
9501         if(serverMoves != NULL) {
9502             fclose(serverMoves);
9503             /* delay 15 sec before overwriting, so all clients can see end */
9504             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9505         }
9506         serverMoves = fopen(appData.serverMovesName, "w");
9507     }
9508
9509     ExitAnalyzeMode();
9510     gameMode = BeginningOfGame;
9511     ModeHighlight();
9512     if(appData.icsActive) gameInfo.variant = VariantNormal;
9513     currentMove = forwardMostMove = backwardMostMove = 0;
9514     InitPosition(redraw);
9515     for (i = 0; i < MAX_MOVES; i++) {
9516         if (commentList[i] != NULL) {
9517             free(commentList[i]);
9518             commentList[i] = NULL;
9519         }
9520     }
9521     ResetClocks();
9522     timeRemaining[0][0] = whiteTimeRemaining;
9523     timeRemaining[1][0] = blackTimeRemaining;
9524     if (first.pr == NULL) {
9525         StartChessProgram(&first);
9526     }
9527     if (init) {
9528             InitChessProgram(&first, startedFromSetupPosition);
9529     }
9530     DisplayTitle("");
9531     DisplayMessage("", "");
9532     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9533     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9534 }
9535
9536 void
9537 AutoPlayGameLoop()
9538 {
9539     for (;;) {
9540         if (!AutoPlayOneMove())
9541           return;
9542         if (matchMode || appData.timeDelay == 0)
9543           continue;
9544         if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
9545           return;
9546         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9547         break;
9548     }
9549 }
9550
9551
9552 int
9553 AutoPlayOneMove()
9554 {
9555     int fromX, fromY, toX, toY;
9556
9557     if (appData.debugMode) {
9558       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9559     }
9560
9561     if (gameMode != PlayFromGameFile)
9562       return FALSE;
9563
9564     if (currentMove >= forwardMostMove) {
9565       gameMode = EditGame;
9566       ModeHighlight();
9567
9568       /* [AS] Clear current move marker at the end of a game */
9569       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9570
9571       return FALSE;
9572     }
9573
9574     toX = moveList[currentMove][2] - AAA;
9575     toY = moveList[currentMove][3] - ONE;
9576
9577     if (moveList[currentMove][1] == '@') {
9578         if (appData.highlightLastMove) {
9579             SetHighlights(-1, -1, toX, toY);
9580         }
9581     } else {
9582         fromX = moveList[currentMove][0] - AAA;
9583         fromY = moveList[currentMove][1] - ONE;
9584
9585         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9586
9587         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9588
9589         if (appData.highlightLastMove) {
9590             SetHighlights(fromX, fromY, toX, toY);
9591         }
9592     }
9593     DisplayMove(currentMove);
9594     SendMoveToProgram(currentMove++, &first);
9595     DisplayBothClocks();
9596     DrawPosition(FALSE, boards[currentMove]);
9597     // [HGM] PV info: always display, routine tests if empty
9598     DisplayComment(currentMove - 1, commentList[currentMove]);
9599     return TRUE;
9600 }
9601
9602
9603 int
9604 LoadGameOneMove(readAhead)
9605      ChessMove readAhead;
9606 {
9607     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9608     char promoChar = NULLCHAR;
9609     ChessMove moveType;
9610     char move[MSG_SIZ];
9611     char *p, *q;
9612
9613     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9614         gameMode != AnalyzeMode && gameMode != Training) {
9615         gameFileFP = NULL;
9616         return FALSE;
9617     }
9618
9619     yyboardindex = forwardMostMove;
9620     if (readAhead != EndOfFile) {
9621       moveType = readAhead;
9622     } else {
9623       if (gameFileFP == NULL)
9624           return FALSE;
9625       moveType = (ChessMove) yylex();
9626     }
9627
9628     done = FALSE;
9629     switch (moveType) {
9630       case Comment:
9631         if (appData.debugMode)
9632           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9633         p = yy_text;
9634
9635         /* append the comment but don't display it */
9636         AppendComment(currentMove, p, FALSE);
9637         return TRUE;
9638
9639       case WhiteCapturesEnPassant:
9640       case BlackCapturesEnPassant:
9641       case WhitePromotion:
9642       case BlackPromotion:
9643       case WhiteNonPromotion:
9644       case BlackNonPromotion:
9645       case NormalMove:
9646       case WhiteKingSideCastle:
9647       case WhiteQueenSideCastle:
9648       case BlackKingSideCastle:
9649       case BlackQueenSideCastle:
9650       case WhiteKingSideCastleWild:
9651       case WhiteQueenSideCastleWild:
9652       case BlackKingSideCastleWild:
9653       case BlackQueenSideCastleWild:
9654       /* PUSH Fabien */
9655       case WhiteHSideCastleFR:
9656       case WhiteASideCastleFR:
9657       case BlackHSideCastleFR:
9658       case BlackASideCastleFR:
9659       /* POP Fabien */
9660         if (appData.debugMode)
9661           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9662         fromX = currentMoveString[0] - AAA;
9663         fromY = currentMoveString[1] - ONE;
9664         toX = currentMoveString[2] - AAA;
9665         toY = currentMoveString[3] - ONE;
9666         promoChar = currentMoveString[4];
9667         break;
9668
9669       case WhiteDrop:
9670       case BlackDrop:
9671         if (appData.debugMode)
9672           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9673         fromX = moveType == WhiteDrop ?
9674           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9675         (int) CharToPiece(ToLower(currentMoveString[0]));
9676         fromY = DROP_RANK;
9677         toX = currentMoveString[2] - AAA;
9678         toY = currentMoveString[3] - ONE;
9679         break;
9680
9681       case WhiteWins:
9682       case BlackWins:
9683       case GameIsDrawn:
9684       case GameUnfinished:
9685         if (appData.debugMode)
9686           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9687         p = strchr(yy_text, '{');
9688         if (p == NULL) p = strchr(yy_text, '(');
9689         if (p == NULL) {
9690             p = yy_text;
9691             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9692         } else {
9693             q = strchr(p, *p == '{' ? '}' : ')');
9694             if (q != NULL) *q = NULLCHAR;
9695             p++;
9696         }
9697         GameEnds(moveType, p, GE_FILE);
9698         done = TRUE;
9699         if (cmailMsgLoaded) {
9700             ClearHighlights();
9701             flipView = WhiteOnMove(currentMove);
9702             if (moveType == GameUnfinished) flipView = !flipView;
9703             if (appData.debugMode)
9704               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
9705         }
9706         break;
9707
9708       case EndOfFile:
9709         if (appData.debugMode)
9710           fprintf(debugFP, "Parser hit end of file\n");
9711         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9712           case MT_NONE:
9713           case MT_CHECK:
9714             break;
9715           case MT_CHECKMATE:
9716           case MT_STAINMATE:
9717             if (WhiteOnMove(currentMove)) {
9718                 GameEnds(BlackWins, "Black mates", GE_FILE);
9719             } else {
9720                 GameEnds(WhiteWins, "White mates", GE_FILE);
9721             }
9722             break;
9723           case MT_STALEMATE:
9724             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9725             break;
9726         }
9727         done = TRUE;
9728         break;
9729
9730       case MoveNumberOne:
9731         if (lastLoadGameStart == GNUChessGame) {
9732             /* GNUChessGames have numbers, but they aren't move numbers */
9733             if (appData.debugMode)
9734               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9735                       yy_text, (int) moveType);
9736             return LoadGameOneMove(EndOfFile); /* tail recursion */
9737         }
9738         /* else fall thru */
9739
9740       case XBoardGame:
9741       case GNUChessGame:
9742       case PGNTag:
9743         /* Reached start of next game in file */
9744         if (appData.debugMode)
9745           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
9746         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9747           case MT_NONE:
9748           case MT_CHECK:
9749             break;
9750           case MT_CHECKMATE:
9751           case MT_STAINMATE:
9752             if (WhiteOnMove(currentMove)) {
9753                 GameEnds(BlackWins, "Black mates", GE_FILE);
9754             } else {
9755                 GameEnds(WhiteWins, "White mates", GE_FILE);
9756             }
9757             break;
9758           case MT_STALEMATE:
9759             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
9760             break;
9761         }
9762         done = TRUE;
9763         break;
9764
9765       case PositionDiagram:     /* should not happen; ignore */
9766       case ElapsedTime:         /* ignore */
9767       case NAG:                 /* ignore */
9768         if (appData.debugMode)
9769           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
9770                   yy_text, (int) moveType);
9771         return LoadGameOneMove(EndOfFile); /* tail recursion */
9772
9773       case IllegalMove:
9774         if (appData.testLegality) {
9775             if (appData.debugMode)
9776               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
9777             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9778                     (forwardMostMove / 2) + 1,
9779                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9780             DisplayError(move, 0);
9781             done = TRUE;
9782         } else {
9783             if (appData.debugMode)
9784               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
9785                       yy_text, currentMoveString);
9786             fromX = currentMoveString[0] - AAA;
9787             fromY = currentMoveString[1] - ONE;
9788             toX = currentMoveString[2] - AAA;
9789             toY = currentMoveString[3] - ONE;
9790             promoChar = currentMoveString[4];
9791         }
9792         break;
9793
9794       case AmbiguousMove:
9795         if (appData.debugMode)
9796           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
9797         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
9798                 (forwardMostMove / 2) + 1,
9799                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9800         DisplayError(move, 0);
9801         done = TRUE;
9802         break;
9803
9804       default:
9805       case ImpossibleMove:
9806         if (appData.debugMode)
9807           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
9808         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
9809                 (forwardMostMove / 2) + 1,
9810                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
9811         DisplayError(move, 0);
9812         done = TRUE;
9813         break;
9814     }
9815
9816     if (done) {
9817         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
9818             DrawPosition(FALSE, boards[currentMove]);
9819             DisplayBothClocks();
9820             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
9821               DisplayComment(currentMove - 1, commentList[currentMove]);
9822         }
9823         (void) StopLoadGameTimer();
9824         gameFileFP = NULL;
9825         cmailOldMove = forwardMostMove;
9826         return FALSE;
9827     } else {
9828         /* currentMoveString is set as a side-effect of yylex */
9829         strcat(currentMoveString, "\n");
9830         safeStrCpy(moveList[forwardMostMove], currentMoveString, sizeof(moveList[forwardMostMove])/sizeof(moveList[forwardMostMove][0]));
9831
9832         thinkOutput[0] = NULLCHAR;
9833         MakeMove(fromX, fromY, toX, toY, promoChar);
9834         currentMove = forwardMostMove;
9835         return TRUE;
9836     }
9837 }
9838
9839 /* Load the nth game from the given file */
9840 int
9841 LoadGameFromFile(filename, n, title, useList)
9842      char *filename;
9843      int n;
9844      char *title;
9845      /*Boolean*/ int useList;
9846 {
9847     FILE *f;
9848     char buf[MSG_SIZ];
9849
9850     if (strcmp(filename, "-") == 0) {
9851         f = stdin;
9852         title = "stdin";
9853     } else {
9854         f = fopen(filename, "rb");
9855         if (f == NULL) {
9856           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
9857             DisplayError(buf, errno);
9858             return FALSE;
9859         }
9860     }
9861     if (fseek(f, 0, 0) == -1) {
9862         /* f is not seekable; probably a pipe */
9863         useList = FALSE;
9864     }
9865     if (useList && n == 0) {
9866         int error = GameListBuild(f);
9867         if (error) {
9868             DisplayError(_("Cannot build game list"), error);
9869         } else if (!ListEmpty(&gameList) &&
9870                    ((ListGame *) gameList.tailPred)->number > 1) {
9871             GameListPopUp(f, title);
9872             return TRUE;
9873         }
9874         GameListDestroy();
9875         n = 1;
9876     }
9877     if (n == 0) n = 1;
9878     return LoadGame(f, n, title, FALSE);
9879 }
9880
9881
9882 void
9883 MakeRegisteredMove()
9884 {
9885     int fromX, fromY, toX, toY;
9886     char promoChar;
9887     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9888         switch (cmailMoveType[lastLoadGameNumber - 1]) {
9889           case CMAIL_MOVE:
9890           case CMAIL_DRAW:
9891             if (appData.debugMode)
9892               fprintf(debugFP, "Restoring %s for game %d\n",
9893                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
9894
9895             thinkOutput[0] = NULLCHAR;
9896             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
9897             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
9898             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
9899             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
9900             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
9901             promoChar = cmailMove[lastLoadGameNumber - 1][4];
9902             MakeMove(fromX, fromY, toX, toY, promoChar);
9903             ShowMove(fromX, fromY, toX, toY);
9904
9905             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
9906               case MT_NONE:
9907               case MT_CHECK:
9908                 break;
9909
9910               case MT_CHECKMATE:
9911               case MT_STAINMATE:
9912                 if (WhiteOnMove(currentMove)) {
9913                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
9914                 } else {
9915                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
9916                 }
9917                 break;
9918
9919               case MT_STALEMATE:
9920                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
9921                 break;
9922             }
9923
9924             break;
9925
9926           case CMAIL_RESIGN:
9927             if (WhiteOnMove(currentMove)) {
9928                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
9929             } else {
9930                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
9931             }
9932             break;
9933
9934           case CMAIL_ACCEPT:
9935             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
9936             break;
9937
9938           default:
9939             break;
9940         }
9941     }
9942
9943     return;
9944 }
9945
9946 /* Wrapper around LoadGame for use when a Cmail message is loaded */
9947 int
9948 CmailLoadGame(f, gameNumber, title, useList)
9949      FILE *f;
9950      int gameNumber;
9951      char *title;
9952      int useList;
9953 {
9954     int retVal;
9955
9956     if (gameNumber > nCmailGames) {
9957         DisplayError(_("No more games in this message"), 0);
9958         return FALSE;
9959     }
9960     if (f == lastLoadGameFP) {
9961         int offset = gameNumber - lastLoadGameNumber;
9962         if (offset == 0) {
9963             cmailMsg[0] = NULLCHAR;
9964             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
9965                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
9966                 nCmailMovesRegistered--;
9967             }
9968             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
9969             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
9970                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
9971             }
9972         } else {
9973             if (! RegisterMove()) return FALSE;
9974         }
9975     }
9976
9977     retVal = LoadGame(f, gameNumber, title, useList);
9978
9979     /* Make move registered during previous look at this game, if any */
9980     MakeRegisteredMove();
9981
9982     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
9983         commentList[currentMove]
9984           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
9985         DisplayComment(currentMove - 1, commentList[currentMove]);
9986     }
9987
9988     return retVal;
9989 }
9990
9991 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
9992 int
9993 ReloadGame(offset)
9994      int offset;
9995 {
9996     int gameNumber = lastLoadGameNumber + offset;
9997     if (lastLoadGameFP == NULL) {
9998         DisplayError(_("No game has been loaded yet"), 0);
9999         return FALSE;
10000     }
10001     if (gameNumber <= 0) {
10002         DisplayError(_("Can't back up any further"), 0);
10003         return FALSE;
10004     }
10005     if (cmailMsgLoaded) {
10006         return CmailLoadGame(lastLoadGameFP, gameNumber,
10007                              lastLoadGameTitle, lastLoadGameUseList);
10008     } else {
10009         return LoadGame(lastLoadGameFP, gameNumber,
10010                         lastLoadGameTitle, lastLoadGameUseList);
10011     }
10012 }
10013
10014
10015
10016 /* Load the nth game from open file f */
10017 int
10018 LoadGame(f, gameNumber, title, useList)
10019      FILE *f;
10020      int gameNumber;
10021      char *title;
10022      int useList;
10023 {
10024     ChessMove cm;
10025     char buf[MSG_SIZ];
10026     int gn = gameNumber;
10027     ListGame *lg = NULL;
10028     int numPGNTags = 0;
10029     int err;
10030     GameMode oldGameMode;
10031     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10032
10033     if (appData.debugMode)
10034         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10035
10036     if (gameMode == Training )
10037         SetTrainingModeOff();
10038
10039     oldGameMode = gameMode;
10040     if (gameMode != BeginningOfGame) {
10041       Reset(FALSE, TRUE);
10042     }
10043
10044     gameFileFP = f;
10045     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10046         fclose(lastLoadGameFP);
10047     }
10048
10049     if (useList) {
10050         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10051
10052         if (lg) {
10053             fseek(f, lg->offset, 0);
10054             GameListHighlight(gameNumber);
10055             gn = 1;
10056         }
10057         else {
10058             DisplayError(_("Game number out of range"), 0);
10059             return FALSE;
10060         }
10061     } else {
10062         GameListDestroy();
10063         if (fseek(f, 0, 0) == -1) {
10064             if (f == lastLoadGameFP ?
10065                 gameNumber == lastLoadGameNumber + 1 :
10066                 gameNumber == 1) {
10067                 gn = 1;
10068             } else {
10069                 DisplayError(_("Can't seek on game file"), 0);
10070                 return FALSE;
10071             }
10072         }
10073     }
10074     lastLoadGameFP = f;
10075     lastLoadGameNumber = gameNumber;
10076     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10077     lastLoadGameUseList = useList;
10078
10079     yynewfile(f);
10080
10081     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10082       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10083                 lg->gameInfo.black);
10084             DisplayTitle(buf);
10085     } else if (*title != NULLCHAR) {
10086         if (gameNumber > 1) {
10087           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10088             DisplayTitle(buf);
10089         } else {
10090             DisplayTitle(title);
10091         }
10092     }
10093
10094     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10095         gameMode = PlayFromGameFile;
10096         ModeHighlight();
10097     }
10098
10099     currentMove = forwardMostMove = backwardMostMove = 0;
10100     CopyBoard(boards[0], initialPosition);
10101     StopClocks();
10102
10103     /*
10104      * Skip the first gn-1 games in the file.
10105      * Also skip over anything that precedes an identifiable
10106      * start of game marker, to avoid being confused by
10107      * garbage at the start of the file.  Currently
10108      * recognized start of game markers are the move number "1",
10109      * the pattern "gnuchess .* game", the pattern
10110      * "^[#;%] [^ ]* game file", and a PGN tag block.
10111      * A game that starts with one of the latter two patterns
10112      * will also have a move number 1, possibly
10113      * following a position diagram.
10114      * 5-4-02: Let's try being more lenient and allowing a game to
10115      * start with an unnumbered move.  Does that break anything?
10116      */
10117     cm = lastLoadGameStart = EndOfFile;
10118     while (gn > 0) {
10119         yyboardindex = forwardMostMove;
10120         cm = (ChessMove) yylex();
10121         switch (cm) {
10122           case EndOfFile:
10123             if (cmailMsgLoaded) {
10124                 nCmailGames = CMAIL_MAX_GAMES - gn;
10125             } else {
10126                 Reset(TRUE, TRUE);
10127                 DisplayError(_("Game not found in file"), 0);
10128             }
10129             return FALSE;
10130
10131           case GNUChessGame:
10132           case XBoardGame:
10133             gn--;
10134             lastLoadGameStart = cm;
10135             break;
10136
10137           case MoveNumberOne:
10138             switch (lastLoadGameStart) {
10139               case GNUChessGame:
10140               case XBoardGame:
10141               case PGNTag:
10142                 break;
10143               case MoveNumberOne:
10144               case EndOfFile:
10145                 gn--;           /* count this game */
10146                 lastLoadGameStart = cm;
10147                 break;
10148               default:
10149                 /* impossible */
10150                 break;
10151             }
10152             break;
10153
10154           case PGNTag:
10155             switch (lastLoadGameStart) {
10156               case GNUChessGame:
10157               case PGNTag:
10158               case MoveNumberOne:
10159               case EndOfFile:
10160                 gn--;           /* count this game */
10161                 lastLoadGameStart = cm;
10162                 break;
10163               case XBoardGame:
10164                 lastLoadGameStart = cm; /* game counted already */
10165                 break;
10166               default:
10167                 /* impossible */
10168                 break;
10169             }
10170             if (gn > 0) {
10171                 do {
10172                     yyboardindex = forwardMostMove;
10173                     cm = (ChessMove) yylex();
10174                 } while (cm == PGNTag || cm == Comment);
10175             }
10176             break;
10177
10178           case WhiteWins:
10179           case BlackWins:
10180           case GameIsDrawn:
10181             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10182                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10183                     != CMAIL_OLD_RESULT) {
10184                     nCmailResults ++ ;
10185                     cmailResult[  CMAIL_MAX_GAMES
10186                                 - gn - 1] = CMAIL_OLD_RESULT;
10187                 }
10188             }
10189             break;
10190
10191           case NormalMove:
10192             /* Only a NormalMove can be at the start of a game
10193              * without a position diagram. */
10194             if (lastLoadGameStart == EndOfFile ) {
10195               gn--;
10196               lastLoadGameStart = MoveNumberOne;
10197             }
10198             break;
10199
10200           default:
10201             break;
10202         }
10203     }
10204
10205     if (appData.debugMode)
10206       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10207
10208     if (cm == XBoardGame) {
10209         /* Skip any header junk before position diagram and/or move 1 */
10210         for (;;) {
10211             yyboardindex = forwardMostMove;
10212             cm = (ChessMove) yylex();
10213
10214             if (cm == EndOfFile ||
10215                 cm == GNUChessGame || cm == XBoardGame) {
10216                 /* Empty game; pretend end-of-file and handle later */
10217                 cm = EndOfFile;
10218                 break;
10219             }
10220
10221             if (cm == MoveNumberOne || cm == PositionDiagram ||
10222                 cm == PGNTag || cm == Comment)
10223               break;
10224         }
10225     } else if (cm == GNUChessGame) {
10226         if (gameInfo.event != NULL) {
10227             free(gameInfo.event);
10228         }
10229         gameInfo.event = StrSave(yy_text);
10230     }
10231
10232     startedFromSetupPosition = FALSE;
10233     while (cm == PGNTag) {
10234         if (appData.debugMode)
10235           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10236         err = ParsePGNTag(yy_text, &gameInfo);
10237         if (!err) numPGNTags++;
10238
10239         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10240         if(gameInfo.variant != oldVariant) {
10241             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10242             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10243             InitPosition(TRUE);
10244             oldVariant = gameInfo.variant;
10245             if (appData.debugMode)
10246               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10247         }
10248
10249
10250         if (gameInfo.fen != NULL) {
10251           Board initial_position;
10252           startedFromSetupPosition = TRUE;
10253           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10254             Reset(TRUE, TRUE);
10255             DisplayError(_("Bad FEN position in file"), 0);
10256             return FALSE;
10257           }
10258           CopyBoard(boards[0], initial_position);
10259           if (blackPlaysFirst) {
10260             currentMove = forwardMostMove = backwardMostMove = 1;
10261             CopyBoard(boards[1], initial_position);
10262             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10263             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10264             timeRemaining[0][1] = whiteTimeRemaining;
10265             timeRemaining[1][1] = blackTimeRemaining;
10266             if (commentList[0] != NULL) {
10267               commentList[1] = commentList[0];
10268               commentList[0] = NULL;
10269             }
10270           } else {
10271             currentMove = forwardMostMove = backwardMostMove = 0;
10272           }
10273           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10274           {   int i;
10275               initialRulePlies = FENrulePlies;
10276               for( i=0; i< nrCastlingRights; i++ )
10277                   initialRights[i] = initial_position[CASTLING][i];
10278           }
10279           yyboardindex = forwardMostMove;
10280           free(gameInfo.fen);
10281           gameInfo.fen = NULL;
10282         }
10283
10284         yyboardindex = forwardMostMove;
10285         cm = (ChessMove) yylex();
10286
10287         /* Handle comments interspersed among the tags */
10288         while (cm == Comment) {
10289             char *p;
10290             if (appData.debugMode)
10291               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10292             p = yy_text;
10293             AppendComment(currentMove, p, FALSE);
10294             yyboardindex = forwardMostMove;
10295             cm = (ChessMove) yylex();
10296         }
10297     }
10298
10299     /* don't rely on existence of Event tag since if game was
10300      * pasted from clipboard the Event tag may not exist
10301      */
10302     if (numPGNTags > 0){
10303         char *tags;
10304         if (gameInfo.variant == VariantNormal) {
10305           VariantClass v = StringToVariant(gameInfo.event);
10306           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10307           if(v < VariantShogi) gameInfo.variant = v;
10308         }
10309         if (!matchMode) {
10310           if( appData.autoDisplayTags ) {
10311             tags = PGNTags(&gameInfo);
10312             TagsPopUp(tags, CmailMsg());
10313             free(tags);
10314           }
10315         }
10316     } else {
10317         /* Make something up, but don't display it now */
10318         SetGameInfo();
10319         TagsPopDown();
10320     }
10321
10322     if (cm == PositionDiagram) {
10323         int i, j;
10324         char *p;
10325         Board initial_position;
10326
10327         if (appData.debugMode)
10328           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10329
10330         if (!startedFromSetupPosition) {
10331             p = yy_text;
10332             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10333               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10334                 switch (*p) {
10335                   case '[':
10336                   case '-':
10337                   case ' ':
10338                   case '\t':
10339                   case '\n':
10340                   case '\r':
10341                     break;
10342                   default:
10343                     initial_position[i][j++] = CharToPiece(*p);
10344                     break;
10345                 }
10346             while (*p == ' ' || *p == '\t' ||
10347                    *p == '\n' || *p == '\r') p++;
10348
10349             if (strncmp(p, "black", strlen("black"))==0)
10350               blackPlaysFirst = TRUE;
10351             else
10352               blackPlaysFirst = FALSE;
10353             startedFromSetupPosition = TRUE;
10354
10355             CopyBoard(boards[0], initial_position);
10356             if (blackPlaysFirst) {
10357                 currentMove = forwardMostMove = backwardMostMove = 1;
10358                 CopyBoard(boards[1], initial_position);
10359                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10360                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10361                 timeRemaining[0][1] = whiteTimeRemaining;
10362                 timeRemaining[1][1] = blackTimeRemaining;
10363                 if (commentList[0] != NULL) {
10364                     commentList[1] = commentList[0];
10365                     commentList[0] = NULL;
10366                 }
10367             } else {
10368                 currentMove = forwardMostMove = backwardMostMove = 0;
10369             }
10370         }
10371         yyboardindex = forwardMostMove;
10372         cm = (ChessMove) yylex();
10373     }
10374
10375     if (first.pr == NoProc) {
10376         StartChessProgram(&first);
10377     }
10378     InitChessProgram(&first, FALSE);
10379     SendToProgram("force\n", &first);
10380     if (startedFromSetupPosition) {
10381         SendBoard(&first, forwardMostMove);
10382     if (appData.debugMode) {
10383         fprintf(debugFP, "Load Game\n");
10384     }
10385         DisplayBothClocks();
10386     }
10387
10388     /* [HGM] server: flag to write setup moves in broadcast file as one */
10389     loadFlag = appData.suppressLoadMoves;
10390
10391     while (cm == Comment) {
10392         char *p;
10393         if (appData.debugMode)
10394           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10395         p = yy_text;
10396         AppendComment(currentMove, p, FALSE);
10397         yyboardindex = forwardMostMove;
10398         cm = (ChessMove) yylex();
10399     }
10400
10401     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10402         cm == WhiteWins || cm == BlackWins ||
10403         cm == GameIsDrawn || cm == GameUnfinished) {
10404         DisplayMessage("", _("No moves in game"));
10405         if (cmailMsgLoaded) {
10406             if (appData.debugMode)
10407               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10408             ClearHighlights();
10409             flipView = FALSE;
10410         }
10411         DrawPosition(FALSE, boards[currentMove]);
10412         DisplayBothClocks();
10413         gameMode = EditGame;
10414         ModeHighlight();
10415         gameFileFP = NULL;
10416         cmailOldMove = 0;
10417         return TRUE;
10418     }
10419
10420     // [HGM] PV info: routine tests if comment empty
10421     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10422         DisplayComment(currentMove - 1, commentList[currentMove]);
10423     }
10424     if (!matchMode && appData.timeDelay != 0)
10425       DrawPosition(FALSE, boards[currentMove]);
10426
10427     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10428       programStats.ok_to_send = 1;
10429     }
10430
10431     /* if the first token after the PGN tags is a move
10432      * and not move number 1, retrieve it from the parser
10433      */
10434     if (cm != MoveNumberOne)
10435         LoadGameOneMove(cm);
10436
10437     /* load the remaining moves from the file */
10438     while (LoadGameOneMove(EndOfFile)) {
10439       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10440       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10441     }
10442
10443     /* rewind to the start of the game */
10444     currentMove = backwardMostMove;
10445
10446     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10447
10448     if (oldGameMode == AnalyzeFile ||
10449         oldGameMode == AnalyzeMode) {
10450       AnalyzeFileEvent();
10451     }
10452
10453     if (matchMode || appData.timeDelay == 0) {
10454       ToEndEvent();
10455       gameMode = EditGame;
10456       ModeHighlight();
10457     } else if (appData.timeDelay > 0) {
10458       AutoPlayGameLoop();
10459     }
10460
10461     if (appData.debugMode)
10462         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10463
10464     loadFlag = 0; /* [HGM] true game starts */
10465     return TRUE;
10466 }
10467
10468 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10469 int
10470 ReloadPosition(offset)
10471      int offset;
10472 {
10473     int positionNumber = lastLoadPositionNumber + offset;
10474     if (lastLoadPositionFP == NULL) {
10475         DisplayError(_("No position has been loaded yet"), 0);
10476         return FALSE;
10477     }
10478     if (positionNumber <= 0) {
10479         DisplayError(_("Can't back up any further"), 0);
10480         return FALSE;
10481     }
10482     return LoadPosition(lastLoadPositionFP, positionNumber,
10483                         lastLoadPositionTitle);
10484 }
10485
10486 /* Load the nth position from the given file */
10487 int
10488 LoadPositionFromFile(filename, n, title)
10489      char *filename;
10490      int n;
10491      char *title;
10492 {
10493     FILE *f;
10494     char buf[MSG_SIZ];
10495
10496     if (strcmp(filename, "-") == 0) {
10497         return LoadPosition(stdin, n, "stdin");
10498     } else {
10499         f = fopen(filename, "rb");
10500         if (f == NULL) {
10501             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10502             DisplayError(buf, errno);
10503             return FALSE;
10504         } else {
10505             return LoadPosition(f, n, title);
10506         }
10507     }
10508 }
10509
10510 /* Load the nth position from the given open file, and close it */
10511 int
10512 LoadPosition(f, positionNumber, title)
10513      FILE *f;
10514      int positionNumber;
10515      char *title;
10516 {
10517     char *p, line[MSG_SIZ];
10518     Board initial_position;
10519     int i, j, fenMode, pn;
10520
10521     if (gameMode == Training )
10522         SetTrainingModeOff();
10523
10524     if (gameMode != BeginningOfGame) {
10525         Reset(FALSE, TRUE);
10526     }
10527     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10528         fclose(lastLoadPositionFP);
10529     }
10530     if (positionNumber == 0) positionNumber = 1;
10531     lastLoadPositionFP = f;
10532     lastLoadPositionNumber = positionNumber;
10533     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10534     if (first.pr == NoProc) {
10535       StartChessProgram(&first);
10536       InitChessProgram(&first, FALSE);
10537     }
10538     pn = positionNumber;
10539     if (positionNumber < 0) {
10540         /* Negative position number means to seek to that byte offset */
10541         if (fseek(f, -positionNumber, 0) == -1) {
10542             DisplayError(_("Can't seek on position file"), 0);
10543             return FALSE;
10544         };
10545         pn = 1;
10546     } else {
10547         if (fseek(f, 0, 0) == -1) {
10548             if (f == lastLoadPositionFP ?
10549                 positionNumber == lastLoadPositionNumber + 1 :
10550                 positionNumber == 1) {
10551                 pn = 1;
10552             } else {
10553                 DisplayError(_("Can't seek on position file"), 0);
10554                 return FALSE;
10555             }
10556         }
10557     }
10558     /* See if this file is FEN or old-style xboard */
10559     if (fgets(line, MSG_SIZ, f) == NULL) {
10560         DisplayError(_("Position not found in file"), 0);
10561         return FALSE;
10562     }
10563     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10564     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10565
10566     if (pn >= 2) {
10567         if (fenMode || line[0] == '#') pn--;
10568         while (pn > 0) {
10569             /* skip positions before number pn */
10570             if (fgets(line, MSG_SIZ, f) == NULL) {
10571                 Reset(TRUE, TRUE);
10572                 DisplayError(_("Position not found in file"), 0);
10573                 return FALSE;
10574             }
10575             if (fenMode || line[0] == '#') pn--;
10576         }
10577     }
10578
10579     if (fenMode) {
10580         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10581             DisplayError(_("Bad FEN position in file"), 0);
10582             return FALSE;
10583         }
10584     } else {
10585         (void) fgets(line, MSG_SIZ, f);
10586         (void) fgets(line, MSG_SIZ, f);
10587
10588         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10589             (void) fgets(line, MSG_SIZ, f);
10590             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10591                 if (*p == ' ')
10592                   continue;
10593                 initial_position[i][j++] = CharToPiece(*p);
10594             }
10595         }
10596
10597         blackPlaysFirst = FALSE;
10598         if (!feof(f)) {
10599             (void) fgets(line, MSG_SIZ, f);
10600             if (strncmp(line, "black", strlen("black"))==0)
10601               blackPlaysFirst = TRUE;
10602         }
10603     }
10604     startedFromSetupPosition = TRUE;
10605
10606     SendToProgram("force\n", &first);
10607     CopyBoard(boards[0], initial_position);
10608     if (blackPlaysFirst) {
10609         currentMove = forwardMostMove = backwardMostMove = 1;
10610         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10611         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10612         CopyBoard(boards[1], initial_position);
10613         DisplayMessage("", _("Black to play"));
10614     } else {
10615         currentMove = forwardMostMove = backwardMostMove = 0;
10616         DisplayMessage("", _("White to play"));
10617     }
10618     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10619     SendBoard(&first, forwardMostMove);
10620     if (appData.debugMode) {
10621 int i, j;
10622   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10623   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10624         fprintf(debugFP, "Load Position\n");
10625     }
10626
10627     if (positionNumber > 1) {
10628       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10629         DisplayTitle(line);
10630     } else {
10631         DisplayTitle(title);
10632     }
10633     gameMode = EditGame;
10634     ModeHighlight();
10635     ResetClocks();
10636     timeRemaining[0][1] = whiteTimeRemaining;
10637     timeRemaining[1][1] = blackTimeRemaining;
10638     DrawPosition(FALSE, boards[currentMove]);
10639
10640     return TRUE;
10641 }
10642
10643
10644 void
10645 CopyPlayerNameIntoFileName(dest, src)
10646      char **dest, *src;
10647 {
10648     while (*src != NULLCHAR && *src != ',') {
10649         if (*src == ' ') {
10650             *(*dest)++ = '_';
10651             src++;
10652         } else {
10653             *(*dest)++ = *src++;
10654         }
10655     }
10656 }
10657
10658 char *DefaultFileName(ext)
10659      char *ext;
10660 {
10661     static char def[MSG_SIZ];
10662     char *p;
10663
10664     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10665         p = def;
10666         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10667         *p++ = '-';
10668         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10669         *p++ = '.';
10670         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10671     } else {
10672         def[0] = NULLCHAR;
10673     }
10674     return def;
10675 }
10676
10677 /* Save the current game to the given file */
10678 int
10679 SaveGameToFile(filename, append)
10680      char *filename;
10681      int append;
10682 {
10683     FILE *f;
10684     char buf[MSG_SIZ];
10685
10686     if (strcmp(filename, "-") == 0) {
10687         return SaveGame(stdout, 0, NULL);
10688     } else {
10689         f = fopen(filename, append ? "a" : "w");
10690         if (f == NULL) {
10691             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10692             DisplayError(buf, errno);
10693             return FALSE;
10694         } else {
10695             return SaveGame(f, 0, NULL);
10696         }
10697     }
10698 }
10699
10700 char *
10701 SavePart(str)
10702      char *str;
10703 {
10704     static char buf[MSG_SIZ];
10705     char *p;
10706
10707     p = strchr(str, ' ');
10708     if (p == NULL) return str;
10709     strncpy(buf, str, p - str);
10710     buf[p - str] = NULLCHAR;
10711     return buf;
10712 }
10713
10714 #define PGN_MAX_LINE 75
10715
10716 #define PGN_SIDE_WHITE  0
10717 #define PGN_SIDE_BLACK  1
10718
10719 /* [AS] */
10720 static int FindFirstMoveOutOfBook( int side )
10721 {
10722     int result = -1;
10723
10724     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
10725         int index = backwardMostMove;
10726         int has_book_hit = 0;
10727
10728         if( (index % 2) != side ) {
10729             index++;
10730         }
10731
10732         while( index < forwardMostMove ) {
10733             /* Check to see if engine is in book */
10734             int depth = pvInfoList[index].depth;
10735             int score = pvInfoList[index].score;
10736             int in_book = 0;
10737
10738             if( depth <= 2 ) {
10739                 in_book = 1;
10740             }
10741             else if( score == 0 && depth == 63 ) {
10742                 in_book = 1; /* Zappa */
10743             }
10744             else if( score == 2 && depth == 99 ) {
10745                 in_book = 1; /* Abrok */
10746             }
10747
10748             has_book_hit += in_book;
10749
10750             if( ! in_book ) {
10751                 result = index;
10752
10753                 break;
10754             }
10755
10756             index += 2;
10757         }
10758     }
10759
10760     return result;
10761 }
10762
10763 /* [AS] */
10764 void GetOutOfBookInfo( char * buf )
10765 {
10766     int oob[2];
10767     int i;
10768     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10769
10770     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
10771     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
10772
10773     *buf = '\0';
10774
10775     if( oob[0] >= 0 || oob[1] >= 0 ) {
10776         for( i=0; i<2; i++ ) {
10777             int idx = oob[i];
10778
10779             if( idx >= 0 ) {
10780                 if( i > 0 && oob[0] >= 0 ) {
10781                     strcat( buf, "   " );
10782                 }
10783
10784                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
10785                 sprintf( buf+strlen(buf), "%s%.2f",
10786                     pvInfoList[idx].score >= 0 ? "+" : "",
10787                     pvInfoList[idx].score / 100.0 );
10788             }
10789         }
10790     }
10791 }
10792
10793 /* Save game in PGN style and close the file */
10794 int
10795 SaveGamePGN(f)
10796      FILE *f;
10797 {
10798     int i, offset, linelen, newblock;
10799     time_t tm;
10800 //    char *movetext;
10801     char numtext[32];
10802     int movelen, numlen, blank;
10803     char move_buffer[100]; /* [AS] Buffer for move+PV info */
10804
10805     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10806
10807     tm = time((time_t *) NULL);
10808
10809     PrintPGNTags(f, &gameInfo);
10810
10811     if (backwardMostMove > 0 || startedFromSetupPosition) {
10812         char *fen = PositionToFEN(backwardMostMove, NULL);
10813         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
10814         fprintf(f, "\n{--------------\n");
10815         PrintPosition(f, backwardMostMove);
10816         fprintf(f, "--------------}\n");
10817         free(fen);
10818     }
10819     else {
10820         /* [AS] Out of book annotation */
10821         if( appData.saveOutOfBookInfo ) {
10822             char buf[64];
10823
10824             GetOutOfBookInfo( buf );
10825
10826             if( buf[0] != '\0' ) {
10827                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
10828             }
10829         }
10830
10831         fprintf(f, "\n");
10832     }
10833
10834     i = backwardMostMove;
10835     linelen = 0;
10836     newblock = TRUE;
10837
10838     while (i < forwardMostMove) {
10839         /* Print comments preceding this move */
10840         if (commentList[i] != NULL) {
10841             if (linelen > 0) fprintf(f, "\n");
10842             fprintf(f, "%s", commentList[i]);
10843             linelen = 0;
10844             newblock = TRUE;
10845         }
10846
10847         /* Format move number */
10848         if ((i % 2) == 0)
10849           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
10850         else
10851           if (newblock)
10852             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
10853           else
10854             numtext[0] = NULLCHAR;
10855
10856         numlen = strlen(numtext);
10857         newblock = FALSE;
10858
10859         /* Print move number */
10860         blank = linelen > 0 && numlen > 0;
10861         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
10862             fprintf(f, "\n");
10863             linelen = 0;
10864             blank = 0;
10865         }
10866         if (blank) {
10867             fprintf(f, " ");
10868             linelen++;
10869         }
10870         fprintf(f, "%s", numtext);
10871         linelen += numlen;
10872
10873         /* Get move */
10874         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
10875         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
10876
10877         /* Print move */
10878         blank = linelen > 0 && movelen > 0;
10879         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10880             fprintf(f, "\n");
10881             linelen = 0;
10882             blank = 0;
10883         }
10884         if (blank) {
10885             fprintf(f, " ");
10886             linelen++;
10887         }
10888         fprintf(f, "%s", move_buffer);
10889         linelen += movelen;
10890
10891         /* [AS] Add PV info if present */
10892         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
10893             /* [HGM] add time */
10894             char buf[MSG_SIZ]; int seconds;
10895
10896             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
10897
10898             if( seconds <= 0)
10899               buf[0] = 0;
10900             else
10901               if( seconds < 30 )
10902                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
10903               else
10904                 {
10905                   seconds = (seconds + 4)/10; // round to full seconds
10906                   if( seconds < 60 )
10907                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
10908                   else
10909                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
10910                 }
10911
10912             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
10913                       pvInfoList[i].score >= 0 ? "+" : "",
10914                       pvInfoList[i].score / 100.0,
10915                       pvInfoList[i].depth,
10916                       buf );
10917
10918             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
10919
10920             /* Print score/depth */
10921             blank = linelen > 0 && movelen > 0;
10922             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
10923                 fprintf(f, "\n");
10924                 linelen = 0;
10925                 blank = 0;
10926             }
10927             if (blank) {
10928                 fprintf(f, " ");
10929                 linelen++;
10930             }
10931             fprintf(f, "%s", move_buffer);
10932             linelen += movelen;
10933         }
10934
10935         i++;
10936     }
10937
10938     /* Start a new line */
10939     if (linelen > 0) fprintf(f, "\n");
10940
10941     /* Print comments after last move */
10942     if (commentList[i] != NULL) {
10943         fprintf(f, "%s\n", commentList[i]);
10944     }
10945
10946     /* Print result */
10947     if (gameInfo.resultDetails != NULL &&
10948         gameInfo.resultDetails[0] != NULLCHAR) {
10949         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
10950                 PGNResult(gameInfo.result));
10951     } else {
10952         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
10953     }
10954
10955     fclose(f);
10956     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
10957     return TRUE;
10958 }
10959
10960 /* Save game in old style and close the file */
10961 int
10962 SaveGameOldStyle(f)
10963      FILE *f;
10964 {
10965     int i, offset;
10966     time_t tm;
10967
10968     tm = time((time_t *) NULL);
10969
10970     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
10971     PrintOpponents(f);
10972
10973     if (backwardMostMove > 0 || startedFromSetupPosition) {
10974         fprintf(f, "\n[--------------\n");
10975         PrintPosition(f, backwardMostMove);
10976         fprintf(f, "--------------]\n");
10977     } else {
10978         fprintf(f, "\n");
10979     }
10980
10981     i = backwardMostMove;
10982     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
10983
10984     while (i < forwardMostMove) {
10985         if (commentList[i] != NULL) {
10986             fprintf(f, "[%s]\n", commentList[i]);
10987         }
10988
10989         if ((i % 2) == 1) {
10990             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
10991             i++;
10992         } else {
10993             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
10994             i++;
10995             if (commentList[i] != NULL) {
10996                 fprintf(f, "\n");
10997                 continue;
10998             }
10999             if (i >= forwardMostMove) {
11000                 fprintf(f, "\n");
11001                 break;
11002             }
11003             fprintf(f, "%s\n", parseList[i]);
11004             i++;
11005         }
11006     }
11007
11008     if (commentList[i] != NULL) {
11009         fprintf(f, "[%s]\n", commentList[i]);
11010     }
11011
11012     /* This isn't really the old style, but it's close enough */
11013     if (gameInfo.resultDetails != NULL &&
11014         gameInfo.resultDetails[0] != NULLCHAR) {
11015         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11016                 gameInfo.resultDetails);
11017     } else {
11018         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11019     }
11020
11021     fclose(f);
11022     return TRUE;
11023 }
11024
11025 /* Save the current game to open file f and close the file */
11026 int
11027 SaveGame(f, dummy, dummy2)
11028      FILE *f;
11029      int dummy;
11030      char *dummy2;
11031 {
11032     if (gameMode == EditPosition) EditPositionDone(TRUE);
11033     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11034     if (appData.oldSaveStyle)
11035       return SaveGameOldStyle(f);
11036     else
11037       return SaveGamePGN(f);
11038 }
11039
11040 /* Save the current position to the given file */
11041 int
11042 SavePositionToFile(filename)
11043      char *filename;
11044 {
11045     FILE *f;
11046     char buf[MSG_SIZ];
11047
11048     if (strcmp(filename, "-") == 0) {
11049         return SavePosition(stdout, 0, NULL);
11050     } else {
11051         f = fopen(filename, "a");
11052         if (f == NULL) {
11053             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11054             DisplayError(buf, errno);
11055             return FALSE;
11056         } else {
11057             SavePosition(f, 0, NULL);
11058             return TRUE;
11059         }
11060     }
11061 }
11062
11063 /* Save the current position to the given open file and close the file */
11064 int
11065 SavePosition(f, dummy, dummy2)
11066      FILE *f;
11067      int dummy;
11068      char *dummy2;
11069 {
11070     time_t tm;
11071     char *fen;
11072
11073     if (gameMode == EditPosition) EditPositionDone(TRUE);
11074     if (appData.oldSaveStyle) {
11075         tm = time((time_t *) NULL);
11076
11077         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11078         PrintOpponents(f);
11079         fprintf(f, "[--------------\n");
11080         PrintPosition(f, currentMove);
11081         fprintf(f, "--------------]\n");
11082     } else {
11083         fen = PositionToFEN(currentMove, NULL);
11084         fprintf(f, "%s\n", fen);
11085         free(fen);
11086     }
11087     fclose(f);
11088     return TRUE;
11089 }
11090
11091 void
11092 ReloadCmailMsgEvent(unregister)
11093      int unregister;
11094 {
11095 #if !WIN32
11096     static char *inFilename = NULL;
11097     static char *outFilename;
11098     int i;
11099     struct stat inbuf, outbuf;
11100     int status;
11101
11102     /* Any registered moves are unregistered if unregister is set, */
11103     /* i.e. invoked by the signal handler */
11104     if (unregister) {
11105         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11106             cmailMoveRegistered[i] = FALSE;
11107             if (cmailCommentList[i] != NULL) {
11108                 free(cmailCommentList[i]);
11109                 cmailCommentList[i] = NULL;
11110             }
11111         }
11112         nCmailMovesRegistered = 0;
11113     }
11114
11115     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11116         cmailResult[i] = CMAIL_NOT_RESULT;
11117     }
11118     nCmailResults = 0;
11119
11120     if (inFilename == NULL) {
11121         /* Because the filenames are static they only get malloced once  */
11122         /* and they never get freed                                      */
11123         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11124         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11125
11126         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11127         sprintf(outFilename, "%s.out", appData.cmailGameName);
11128     }
11129
11130     status = stat(outFilename, &outbuf);
11131     if (status < 0) {
11132         cmailMailedMove = FALSE;
11133     } else {
11134         status = stat(inFilename, &inbuf);
11135         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11136     }
11137
11138     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11139        counts the games, notes how each one terminated, etc.
11140
11141        It would be nice to remove this kludge and instead gather all
11142        the information while building the game list.  (And to keep it
11143        in the game list nodes instead of having a bunch of fixed-size
11144        parallel arrays.)  Note this will require getting each game's
11145        termination from the PGN tags, as the game list builder does
11146        not process the game moves.  --mann
11147        */
11148     cmailMsgLoaded = TRUE;
11149     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11150
11151     /* Load first game in the file or popup game menu */
11152     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11153
11154 #endif /* !WIN32 */
11155     return;
11156 }
11157
11158 int
11159 RegisterMove()
11160 {
11161     FILE *f;
11162     char string[MSG_SIZ];
11163
11164     if (   cmailMailedMove
11165         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11166         return TRUE;            /* Allow free viewing  */
11167     }
11168
11169     /* Unregister move to ensure that we don't leave RegisterMove        */
11170     /* with the move registered when the conditions for registering no   */
11171     /* longer hold                                                       */
11172     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11173         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11174         nCmailMovesRegistered --;
11175
11176         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11177           {
11178               free(cmailCommentList[lastLoadGameNumber - 1]);
11179               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11180           }
11181     }
11182
11183     if (cmailOldMove == -1) {
11184         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11185         return FALSE;
11186     }
11187
11188     if (currentMove > cmailOldMove + 1) {
11189         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11190         return FALSE;
11191     }
11192
11193     if (currentMove < cmailOldMove) {
11194         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11195         return FALSE;
11196     }
11197
11198     if (forwardMostMove > currentMove) {
11199         /* Silently truncate extra moves */
11200         TruncateGame();
11201     }
11202
11203     if (   (currentMove == cmailOldMove + 1)
11204         || (   (currentMove == cmailOldMove)
11205             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11206                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11207         if (gameInfo.result != GameUnfinished) {
11208             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11209         }
11210
11211         if (commentList[currentMove] != NULL) {
11212             cmailCommentList[lastLoadGameNumber - 1]
11213               = StrSave(commentList[currentMove]);
11214         }
11215         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11216
11217         if (appData.debugMode)
11218           fprintf(debugFP, "Saving %s for game %d\n",
11219                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11220
11221         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11222
11223         f = fopen(string, "w");
11224         if (appData.oldSaveStyle) {
11225             SaveGameOldStyle(f); /* also closes the file */
11226
11227             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11228             f = fopen(string, "w");
11229             SavePosition(f, 0, NULL); /* also closes the file */
11230         } else {
11231             fprintf(f, "{--------------\n");
11232             PrintPosition(f, currentMove);
11233             fprintf(f, "--------------}\n\n");
11234
11235             SaveGame(f, 0, NULL); /* also closes the file*/
11236         }
11237
11238         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11239         nCmailMovesRegistered ++;
11240     } else if (nCmailGames == 1) {
11241         DisplayError(_("You have not made a move yet"), 0);
11242         return FALSE;
11243     }
11244
11245     return TRUE;
11246 }
11247
11248 void
11249 MailMoveEvent()
11250 {
11251 #if !WIN32
11252     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11253     FILE *commandOutput;
11254     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11255     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11256     int nBuffers;
11257     int i;
11258     int archived;
11259     char *arcDir;
11260
11261     if (! cmailMsgLoaded) {
11262         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11263         return;
11264     }
11265
11266     if (nCmailGames == nCmailResults) {
11267         DisplayError(_("No unfinished games"), 0);
11268         return;
11269     }
11270
11271 #if CMAIL_PROHIBIT_REMAIL
11272     if (cmailMailedMove) {
11273       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);
11274         DisplayError(msg, 0);
11275         return;
11276     }
11277 #endif
11278
11279     if (! (cmailMailedMove || RegisterMove())) return;
11280
11281     if (   cmailMailedMove
11282         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11283       snprintf(string, MSG_SIZ, partCommandString,
11284                appData.debugMode ? " -v" : "", appData.cmailGameName);
11285         commandOutput = popen(string, "r");
11286
11287         if (commandOutput == NULL) {
11288             DisplayError(_("Failed to invoke cmail"), 0);
11289         } else {
11290             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11291                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11292             }
11293             if (nBuffers > 1) {
11294                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11295                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11296                 nBytes = MSG_SIZ - 1;
11297             } else {
11298                 (void) memcpy(msg, buffer, nBytes);
11299             }
11300             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11301
11302             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11303                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11304
11305                 archived = TRUE;
11306                 for (i = 0; i < nCmailGames; i ++) {
11307                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11308                         archived = FALSE;
11309                     }
11310                 }
11311                 if (   archived
11312                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11313                         != NULL)) {
11314                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11315                            arcDir,
11316                            appData.cmailGameName,
11317                            gameInfo.date);
11318                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11319                     cmailMsgLoaded = FALSE;
11320                 }
11321             }
11322
11323             DisplayInformation(msg);
11324             pclose(commandOutput);
11325         }
11326     } else {
11327         if ((*cmailMsg) != '\0') {
11328             DisplayInformation(cmailMsg);
11329         }
11330     }
11331
11332     return;
11333 #endif /* !WIN32 */
11334 }
11335
11336 char *
11337 CmailMsg()
11338 {
11339 #if WIN32
11340     return NULL;
11341 #else
11342     int  prependComma = 0;
11343     char number[5];
11344     char string[MSG_SIZ];       /* Space for game-list */
11345     int  i;
11346
11347     if (!cmailMsgLoaded) return "";
11348
11349     if (cmailMailedMove) {
11350       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11351     } else {
11352         /* Create a list of games left */
11353       snprintf(string, MSG_SIZ, "[");
11354         for (i = 0; i < nCmailGames; i ++) {
11355             if (! (   cmailMoveRegistered[i]
11356                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11357                 if (prependComma) {
11358                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11359                 } else {
11360                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11361                     prependComma = 1;
11362                 }
11363
11364                 strcat(string, number);
11365             }
11366         }
11367         strcat(string, "]");
11368
11369         if (nCmailMovesRegistered + nCmailResults == 0) {
11370             switch (nCmailGames) {
11371               case 1:
11372                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11373                 break;
11374
11375               case 2:
11376                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11377                 break;
11378
11379               default:
11380                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11381                          nCmailGames);
11382                 break;
11383             }
11384         } else {
11385             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11386               case 1:
11387                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11388                          string);
11389                 break;
11390
11391               case 0:
11392                 if (nCmailResults == nCmailGames) {
11393                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11394                 } else {
11395                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11396                 }
11397                 break;
11398
11399               default:
11400                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11401                          string);
11402             }
11403         }
11404     }
11405     return cmailMsg;
11406 #endif /* WIN32 */
11407 }
11408
11409 void
11410 ResetGameEvent()
11411 {
11412     if (gameMode == Training)
11413       SetTrainingModeOff();
11414
11415     Reset(TRUE, TRUE);
11416     cmailMsgLoaded = FALSE;
11417     if (appData.icsActive) {
11418       SendToICS(ics_prefix);
11419       SendToICS("refresh\n");
11420     }
11421 }
11422
11423 void
11424 ExitEvent(status)
11425      int status;
11426 {
11427     exiting++;
11428     if (exiting > 2) {
11429       /* Give up on clean exit */
11430       exit(status);
11431     }
11432     if (exiting > 1) {
11433       /* Keep trying for clean exit */
11434       return;
11435     }
11436
11437     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11438
11439     if (telnetISR != NULL) {
11440       RemoveInputSource(telnetISR);
11441     }
11442     if (icsPR != NoProc) {
11443       DestroyChildProcess(icsPR, TRUE);
11444     }
11445
11446     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11447     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11448
11449     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11450     /* make sure this other one finishes before killing it!                  */
11451     if(endingGame) { int count = 0;
11452         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11453         while(endingGame && count++ < 10) DoSleep(1);
11454         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11455     }
11456
11457     /* Kill off chess programs */
11458     if (first.pr != NoProc) {
11459         ExitAnalyzeMode();
11460
11461         DoSleep( appData.delayBeforeQuit );
11462         SendToProgram("quit\n", &first);
11463         DoSleep( appData.delayAfterQuit );
11464         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11465     }
11466     if (second.pr != NoProc) {
11467         DoSleep( appData.delayBeforeQuit );
11468         SendToProgram("quit\n", &second);
11469         DoSleep( appData.delayAfterQuit );
11470         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11471     }
11472     if (first.isr != NULL) {
11473         RemoveInputSource(first.isr);
11474     }
11475     if (second.isr != NULL) {
11476         RemoveInputSource(second.isr);
11477     }
11478
11479     ShutDownFrontEnd();
11480     exit(status);
11481 }
11482
11483 void
11484 PauseEvent()
11485 {
11486     if (appData.debugMode)
11487         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11488     if (pausing) {
11489         pausing = FALSE;
11490         ModeHighlight();
11491         if (gameMode == MachinePlaysWhite ||
11492             gameMode == MachinePlaysBlack) {
11493             StartClocks();
11494         } else {
11495             DisplayBothClocks();
11496         }
11497         if (gameMode == PlayFromGameFile) {
11498             if (appData.timeDelay >= 0)
11499                 AutoPlayGameLoop();
11500         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11501             Reset(FALSE, TRUE);
11502             SendToICS(ics_prefix);
11503             SendToICS("refresh\n");
11504         } else if (currentMove < forwardMostMove) {
11505             ForwardInner(forwardMostMove);
11506         }
11507         pauseExamInvalid = FALSE;
11508     } else {
11509         switch (gameMode) {
11510           default:
11511             return;
11512           case IcsExamining:
11513             pauseExamForwardMostMove = forwardMostMove;
11514             pauseExamInvalid = FALSE;
11515             /* fall through */
11516           case IcsObserving:
11517           case IcsPlayingWhite:
11518           case IcsPlayingBlack:
11519             pausing = TRUE;
11520             ModeHighlight();
11521             return;
11522           case PlayFromGameFile:
11523             (void) StopLoadGameTimer();
11524             pausing = TRUE;
11525             ModeHighlight();
11526             break;
11527           case BeginningOfGame:
11528             if (appData.icsActive) return;
11529             /* else fall through */
11530           case MachinePlaysWhite:
11531           case MachinePlaysBlack:
11532           case TwoMachinesPlay:
11533             if (forwardMostMove == 0)
11534               return;           /* don't pause if no one has moved */
11535             if ((gameMode == MachinePlaysWhite &&
11536                  !WhiteOnMove(forwardMostMove)) ||
11537                 (gameMode == MachinePlaysBlack &&
11538                  WhiteOnMove(forwardMostMove))) {
11539                 StopClocks();
11540             }
11541             pausing = TRUE;
11542             ModeHighlight();
11543             break;
11544         }
11545     }
11546 }
11547
11548 void
11549 EditCommentEvent()
11550 {
11551     char title[MSG_SIZ];
11552
11553     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11554       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11555     } else {
11556       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11557                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11558                parseList[currentMove - 1]);
11559     }
11560
11561     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11562 }
11563
11564
11565 void
11566 EditTagsEvent()
11567 {
11568     char *tags = PGNTags(&gameInfo);
11569     EditTagsPopUp(tags);
11570     free(tags);
11571 }
11572
11573 void
11574 AnalyzeModeEvent()
11575 {
11576     if (appData.noChessProgram || gameMode == AnalyzeMode)
11577       return;
11578
11579     if (gameMode != AnalyzeFile) {
11580         if (!appData.icsEngineAnalyze) {
11581                EditGameEvent();
11582                if (gameMode != EditGame) return;
11583         }
11584         ResurrectChessProgram();
11585         SendToProgram("analyze\n", &first);
11586         first.analyzing = TRUE;
11587         /*first.maybeThinking = TRUE;*/
11588         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11589         EngineOutputPopUp();
11590     }
11591     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11592     pausing = FALSE;
11593     ModeHighlight();
11594     SetGameInfo();
11595
11596     StartAnalysisClock();
11597     GetTimeMark(&lastNodeCountTime);
11598     lastNodeCount = 0;
11599 }
11600
11601 void
11602 AnalyzeFileEvent()
11603 {
11604     if (appData.noChessProgram || gameMode == AnalyzeFile)
11605       return;
11606
11607     if (gameMode != AnalyzeMode) {
11608         EditGameEvent();
11609         if (gameMode != EditGame) return;
11610         ResurrectChessProgram();
11611         SendToProgram("analyze\n", &first);
11612         first.analyzing = TRUE;
11613         /*first.maybeThinking = TRUE;*/
11614         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11615         EngineOutputPopUp();
11616     }
11617     gameMode = AnalyzeFile;
11618     pausing = FALSE;
11619     ModeHighlight();
11620     SetGameInfo();
11621
11622     StartAnalysisClock();
11623     GetTimeMark(&lastNodeCountTime);
11624     lastNodeCount = 0;
11625 }
11626
11627 void
11628 MachineWhiteEvent()
11629 {
11630     char buf[MSG_SIZ];
11631     char *bookHit = NULL;
11632
11633     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11634       return;
11635
11636
11637     if (gameMode == PlayFromGameFile ||
11638         gameMode == TwoMachinesPlay  ||
11639         gameMode == Training         ||
11640         gameMode == AnalyzeMode      ||
11641         gameMode == EndOfGame)
11642         EditGameEvent();
11643
11644     if (gameMode == EditPosition)
11645         EditPositionDone(TRUE);
11646
11647     if (!WhiteOnMove(currentMove)) {
11648         DisplayError(_("It is not White's turn"), 0);
11649         return;
11650     }
11651
11652     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11653       ExitAnalyzeMode();
11654
11655     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11656         gameMode == AnalyzeFile)
11657         TruncateGame();
11658
11659     ResurrectChessProgram();    /* in case it isn't running */
11660     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11661         gameMode = MachinePlaysWhite;
11662         ResetClocks();
11663     } else
11664     gameMode = MachinePlaysWhite;
11665     pausing = FALSE;
11666     ModeHighlight();
11667     SetGameInfo();
11668     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11669     DisplayTitle(buf);
11670     if (first.sendName) {
11671       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11672       SendToProgram(buf, &first);
11673     }
11674     if (first.sendTime) {
11675       if (first.useColors) {
11676         SendToProgram("black\n", &first); /*gnu kludge*/
11677       }
11678       SendTimeRemaining(&first, TRUE);
11679     }
11680     if (first.useColors) {
11681       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
11682     }
11683     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11684     SetMachineThinkingEnables();
11685     first.maybeThinking = TRUE;
11686     StartClocks();
11687     firstMove = FALSE;
11688
11689     if (appData.autoFlipView && !flipView) {
11690       flipView = !flipView;
11691       DrawPosition(FALSE, NULL);
11692       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11693     }
11694
11695     if(bookHit) { // [HGM] book: simulate book reply
11696         static char bookMove[MSG_SIZ]; // a bit generous?
11697
11698         programStats.nodes = programStats.depth = programStats.time =
11699         programStats.score = programStats.got_only_move = 0;
11700         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11701
11702         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11703         strcat(bookMove, bookHit);
11704         HandleMachineMove(bookMove, &first);
11705     }
11706 }
11707
11708 void
11709 MachineBlackEvent()
11710 {
11711   char buf[MSG_SIZ];
11712   char *bookHit = NULL;
11713
11714     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
11715         return;
11716
11717
11718     if (gameMode == PlayFromGameFile ||
11719         gameMode == TwoMachinesPlay  ||
11720         gameMode == Training         ||
11721         gameMode == AnalyzeMode      ||
11722         gameMode == EndOfGame)
11723         EditGameEvent();
11724
11725     if (gameMode == EditPosition)
11726         EditPositionDone(TRUE);
11727
11728     if (WhiteOnMove(currentMove)) {
11729         DisplayError(_("It is not Black's turn"), 0);
11730         return;
11731     }
11732
11733     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11734       ExitAnalyzeMode();
11735
11736     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11737         gameMode == AnalyzeFile)
11738         TruncateGame();
11739
11740     ResurrectChessProgram();    /* in case it isn't running */
11741     gameMode = MachinePlaysBlack;
11742     pausing = FALSE;
11743     ModeHighlight();
11744     SetGameInfo();
11745     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11746     DisplayTitle(buf);
11747     if (first.sendName) {
11748       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
11749       SendToProgram(buf, &first);
11750     }
11751     if (first.sendTime) {
11752       if (first.useColors) {
11753         SendToProgram("white\n", &first); /*gnu kludge*/
11754       }
11755       SendTimeRemaining(&first, FALSE);
11756     }
11757     if (first.useColors) {
11758       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
11759     }
11760     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
11761     SetMachineThinkingEnables();
11762     first.maybeThinking = TRUE;
11763     StartClocks();
11764
11765     if (appData.autoFlipView && flipView) {
11766       flipView = !flipView;
11767       DrawPosition(FALSE, NULL);
11768       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
11769     }
11770     if(bookHit) { // [HGM] book: simulate book reply
11771         static char bookMove[MSG_SIZ]; // a bit generous?
11772
11773         programStats.nodes = programStats.depth = programStats.time =
11774         programStats.score = programStats.got_only_move = 0;
11775         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11776
11777         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11778         strcat(bookMove, bookHit);
11779         HandleMachineMove(bookMove, &first);
11780     }
11781 }
11782
11783
11784 void
11785 DisplayTwoMachinesTitle()
11786 {
11787     char buf[MSG_SIZ];
11788     if (appData.matchGames > 0) {
11789         if (first.twoMachinesColor[0] == 'w') {
11790           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11791                    gameInfo.white, gameInfo.black,
11792                    first.matchWins, second.matchWins,
11793                    matchGame - 1 - (first.matchWins + second.matchWins));
11794         } else {
11795           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
11796                    gameInfo.white, gameInfo.black,
11797                    second.matchWins, first.matchWins,
11798                    matchGame - 1 - (first.matchWins + second.matchWins));
11799         }
11800     } else {
11801       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11802     }
11803     DisplayTitle(buf);
11804 }
11805
11806 void
11807 TwoMachinesEvent P((void))
11808 {
11809     int i;
11810     char buf[MSG_SIZ];
11811     ChessProgramState *onmove;
11812     char *bookHit = NULL;
11813
11814     if (appData.noChessProgram) return;
11815
11816     switch (gameMode) {
11817       case TwoMachinesPlay:
11818         return;
11819       case MachinePlaysWhite:
11820       case MachinePlaysBlack:
11821         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
11822             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
11823             return;
11824         }
11825         /* fall through */
11826       case BeginningOfGame:
11827       case PlayFromGameFile:
11828       case EndOfGame:
11829         EditGameEvent();
11830         if (gameMode != EditGame) return;
11831         break;
11832       case EditPosition:
11833         EditPositionDone(TRUE);
11834         break;
11835       case AnalyzeMode:
11836       case AnalyzeFile:
11837         ExitAnalyzeMode();
11838         break;
11839       case EditGame:
11840       default:
11841         break;
11842     }
11843
11844 //    forwardMostMove = currentMove;
11845     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
11846     ResurrectChessProgram();    /* in case first program isn't running */
11847
11848     if (second.pr == NULL) {
11849         StartChessProgram(&second);
11850         if (second.protocolVersion == 1) {
11851           TwoMachinesEventIfReady();
11852         } else {
11853           /* kludge: allow timeout for initial "feature" command */
11854           FreezeUI();
11855           DisplayMessage("", _("Starting second chess program"));
11856           ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
11857         }
11858         return;
11859     }
11860     DisplayMessage("", "");
11861     InitChessProgram(&second, FALSE);
11862     SendToProgram("force\n", &second);
11863     if (startedFromSetupPosition) {
11864         SendBoard(&second, backwardMostMove);
11865     if (appData.debugMode) {
11866         fprintf(debugFP, "Two Machines\n");
11867     }
11868     }
11869     for (i = backwardMostMove; i < forwardMostMove; i++) {
11870         SendMoveToProgram(i, &second);
11871     }
11872
11873     gameMode = TwoMachinesPlay;
11874     pausing = FALSE;
11875     ModeHighlight();
11876     SetGameInfo();
11877     DisplayTwoMachinesTitle();
11878     firstMove = TRUE;
11879     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
11880         onmove = &first;
11881     } else {
11882         onmove = &second;
11883     }
11884
11885     SendToProgram(first.computerString, &first);
11886     if (first.sendName) {
11887       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
11888       SendToProgram(buf, &first);
11889     }
11890     SendToProgram(second.computerString, &second);
11891     if (second.sendName) {
11892       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
11893       SendToProgram(buf, &second);
11894     }
11895
11896     ResetClocks();
11897     if (!first.sendTime || !second.sendTime) {
11898         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11899         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11900     }
11901     if (onmove->sendTime) {
11902       if (onmove->useColors) {
11903         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
11904       }
11905       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
11906     }
11907     if (onmove->useColors) {
11908       SendToProgram(onmove->twoMachinesColor, onmove);
11909     }
11910     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
11911 //    SendToProgram("go\n", onmove);
11912     onmove->maybeThinking = TRUE;
11913     SetMachineThinkingEnables();
11914
11915     StartClocks();
11916
11917     if(bookHit) { // [HGM] book: simulate book reply
11918         static char bookMove[MSG_SIZ]; // a bit generous?
11919
11920         programStats.nodes = programStats.depth = programStats.time =
11921         programStats.score = programStats.got_only_move = 0;
11922         sprintf(programStats.movelist, "%s (xbook)", bookHit);
11923
11924         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
11925         strcat(bookMove, bookHit);
11926         savedMessage = bookMove; // args for deferred call
11927         savedState = onmove;
11928         ScheduleDelayedEvent(DeferredBookMove, 1);
11929     }
11930 }
11931
11932 void
11933 TrainingEvent()
11934 {
11935     if (gameMode == Training) {
11936       SetTrainingModeOff();
11937       gameMode = PlayFromGameFile;
11938       DisplayMessage("", _("Training mode off"));
11939     } else {
11940       gameMode = Training;
11941       animateTraining = appData.animate;
11942
11943       /* make sure we are not already at the end of the game */
11944       if (currentMove < forwardMostMove) {
11945         SetTrainingModeOn();
11946         DisplayMessage("", _("Training mode on"));
11947       } else {
11948         gameMode = PlayFromGameFile;
11949         DisplayError(_("Already at end of game"), 0);
11950       }
11951     }
11952     ModeHighlight();
11953 }
11954
11955 void
11956 IcsClientEvent()
11957 {
11958     if (!appData.icsActive) return;
11959     switch (gameMode) {
11960       case IcsPlayingWhite:
11961       case IcsPlayingBlack:
11962       case IcsObserving:
11963       case IcsIdle:
11964       case BeginningOfGame:
11965       case IcsExamining:
11966         return;
11967
11968       case EditGame:
11969         break;
11970
11971       case EditPosition:
11972         EditPositionDone(TRUE);
11973         break;
11974
11975       case AnalyzeMode:
11976       case AnalyzeFile:
11977         ExitAnalyzeMode();
11978         break;
11979
11980       default:
11981         EditGameEvent();
11982         break;
11983     }
11984
11985     gameMode = IcsIdle;
11986     ModeHighlight();
11987     return;
11988 }
11989
11990
11991 void
11992 EditGameEvent()
11993 {
11994     int i;
11995
11996     switch (gameMode) {
11997       case Training:
11998         SetTrainingModeOff();
11999         break;
12000       case MachinePlaysWhite:
12001       case MachinePlaysBlack:
12002       case BeginningOfGame:
12003         SendToProgram("force\n", &first);
12004         SetUserThinkingEnables();
12005         break;
12006       case PlayFromGameFile:
12007         (void) StopLoadGameTimer();
12008         if (gameFileFP != NULL) {
12009             gameFileFP = NULL;
12010         }
12011         break;
12012       case EditPosition:
12013         EditPositionDone(TRUE);
12014         break;
12015       case AnalyzeMode:
12016       case AnalyzeFile:
12017         ExitAnalyzeMode();
12018         SendToProgram("force\n", &first);
12019         break;
12020       case TwoMachinesPlay:
12021         GameEnds(EndOfFile, NULL, GE_PLAYER);
12022         ResurrectChessProgram();
12023         SetUserThinkingEnables();
12024         break;
12025       case EndOfGame:
12026         ResurrectChessProgram();
12027         break;
12028       case IcsPlayingBlack:
12029       case IcsPlayingWhite:
12030         DisplayError(_("Warning: You are still playing a game"), 0);
12031         break;
12032       case IcsObserving:
12033         DisplayError(_("Warning: You are still observing a game"), 0);
12034         break;
12035       case IcsExamining:
12036         DisplayError(_("Warning: You are still examining a game"), 0);
12037         break;
12038       case IcsIdle:
12039         break;
12040       case EditGame:
12041       default:
12042         return;
12043     }
12044
12045     pausing = FALSE;
12046     StopClocks();
12047     first.offeredDraw = second.offeredDraw = 0;
12048
12049     if (gameMode == PlayFromGameFile) {
12050         whiteTimeRemaining = timeRemaining[0][currentMove];
12051         blackTimeRemaining = timeRemaining[1][currentMove];
12052         DisplayTitle("");
12053     }
12054
12055     if (gameMode == MachinePlaysWhite ||
12056         gameMode == MachinePlaysBlack ||
12057         gameMode == TwoMachinesPlay ||
12058         gameMode == EndOfGame) {
12059         i = forwardMostMove;
12060         while (i > currentMove) {
12061             SendToProgram("undo\n", &first);
12062             i--;
12063         }
12064         whiteTimeRemaining = timeRemaining[0][currentMove];
12065         blackTimeRemaining = timeRemaining[1][currentMove];
12066         DisplayBothClocks();
12067         if (whiteFlag || blackFlag) {
12068             whiteFlag = blackFlag = 0;
12069         }
12070         DisplayTitle("");
12071     }
12072
12073     gameMode = EditGame;
12074     ModeHighlight();
12075     SetGameInfo();
12076 }
12077
12078
12079 void
12080 EditPositionEvent()
12081 {
12082     if (gameMode == EditPosition) {
12083         EditGameEvent();
12084         return;
12085     }
12086
12087     EditGameEvent();
12088     if (gameMode != EditGame) return;
12089
12090     gameMode = EditPosition;
12091     ModeHighlight();
12092     SetGameInfo();
12093     if (currentMove > 0)
12094       CopyBoard(boards[0], boards[currentMove]);
12095
12096     blackPlaysFirst = !WhiteOnMove(currentMove);
12097     ResetClocks();
12098     currentMove = forwardMostMove = backwardMostMove = 0;
12099     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12100     DisplayMove(-1);
12101 }
12102
12103 void
12104 ExitAnalyzeMode()
12105 {
12106     /* [DM] icsEngineAnalyze - possible call from other functions */
12107     if (appData.icsEngineAnalyze) {
12108         appData.icsEngineAnalyze = FALSE;
12109
12110         DisplayMessage("",_("Close ICS engine analyze..."));
12111     }
12112     if (first.analysisSupport && first.analyzing) {
12113       SendToProgram("exit\n", &first);
12114       first.analyzing = FALSE;
12115     }
12116     thinkOutput[0] = NULLCHAR;
12117 }
12118
12119 void
12120 EditPositionDone(Boolean fakeRights)
12121 {
12122     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12123
12124     startedFromSetupPosition = TRUE;
12125     InitChessProgram(&first, FALSE);
12126     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12127       boards[0][EP_STATUS] = EP_NONE;
12128       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12129     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12130         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12131         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12132       } else boards[0][CASTLING][2] = NoRights;
12133     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12134         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12135         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12136       } else boards[0][CASTLING][5] = NoRights;
12137     }
12138     SendToProgram("force\n", &first);
12139     if (blackPlaysFirst) {
12140         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12141         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12142         currentMove = forwardMostMove = backwardMostMove = 1;
12143         CopyBoard(boards[1], boards[0]);
12144     } else {
12145         currentMove = forwardMostMove = backwardMostMove = 0;
12146     }
12147     SendBoard(&first, forwardMostMove);
12148     if (appData.debugMode) {
12149         fprintf(debugFP, "EditPosDone\n");
12150     }
12151     DisplayTitle("");
12152     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12153     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12154     gameMode = EditGame;
12155     ModeHighlight();
12156     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12157     ClearHighlights(); /* [AS] */
12158 }
12159
12160 /* Pause for `ms' milliseconds */
12161 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12162 void
12163 TimeDelay(ms)
12164      long ms;
12165 {
12166     TimeMark m1, m2;
12167
12168     GetTimeMark(&m1);
12169     do {
12170         GetTimeMark(&m2);
12171     } while (SubtractTimeMarks(&m2, &m1) < ms);
12172 }
12173
12174 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12175 void
12176 SendMultiLineToICS(buf)
12177      char *buf;
12178 {
12179     char temp[MSG_SIZ+1], *p;
12180     int len;
12181
12182     len = strlen(buf);
12183     if (len > MSG_SIZ)
12184       len = MSG_SIZ;
12185
12186     strncpy(temp, buf, len);
12187     temp[len] = 0;
12188
12189     p = temp;
12190     while (*p) {
12191         if (*p == '\n' || *p == '\r')
12192           *p = ' ';
12193         ++p;
12194     }
12195
12196     strcat(temp, "\n");
12197     SendToICS(temp);
12198     SendToPlayer(temp, strlen(temp));
12199 }
12200
12201 void
12202 SetWhiteToPlayEvent()
12203 {
12204     if (gameMode == EditPosition) {
12205         blackPlaysFirst = FALSE;
12206         DisplayBothClocks();    /* works because currentMove is 0 */
12207     } else if (gameMode == IcsExamining) {
12208         SendToICS(ics_prefix);
12209         SendToICS("tomove white\n");
12210     }
12211 }
12212
12213 void
12214 SetBlackToPlayEvent()
12215 {
12216     if (gameMode == EditPosition) {
12217         blackPlaysFirst = TRUE;
12218         currentMove = 1;        /* kludge */
12219         DisplayBothClocks();
12220         currentMove = 0;
12221     } else if (gameMode == IcsExamining) {
12222         SendToICS(ics_prefix);
12223         SendToICS("tomove black\n");
12224     }
12225 }
12226
12227 void
12228 EditPositionMenuEvent(selection, x, y)
12229      ChessSquare selection;
12230      int x, y;
12231 {
12232     char buf[MSG_SIZ];
12233     ChessSquare piece = boards[0][y][x];
12234
12235     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12236
12237     switch (selection) {
12238       case ClearBoard:
12239         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12240             SendToICS(ics_prefix);
12241             SendToICS("bsetup clear\n");
12242         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12243             SendToICS(ics_prefix);
12244             SendToICS("clearboard\n");
12245         } else {
12246             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12247                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12248                 for (y = 0; y < BOARD_HEIGHT; y++) {
12249                     if (gameMode == IcsExamining) {
12250                         if (boards[currentMove][y][x] != EmptySquare) {
12251                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12252                                     AAA + x, ONE + y);
12253                             SendToICS(buf);
12254                         }
12255                     } else {
12256                         boards[0][y][x] = p;
12257                     }
12258                 }
12259             }
12260         }
12261         if (gameMode == EditPosition) {
12262             DrawPosition(FALSE, boards[0]);
12263         }
12264         break;
12265
12266       case WhitePlay:
12267         SetWhiteToPlayEvent();
12268         break;
12269
12270       case BlackPlay:
12271         SetBlackToPlayEvent();
12272         break;
12273
12274       case EmptySquare:
12275         if (gameMode == IcsExamining) {
12276             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12277             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12278             SendToICS(buf);
12279         } else {
12280             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12281                 if(x == BOARD_LEFT-2) {
12282                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12283                     boards[0][y][1] = 0;
12284                 } else
12285                 if(x == BOARD_RGHT+1) {
12286                     if(y >= gameInfo.holdingsSize) break;
12287                     boards[0][y][BOARD_WIDTH-2] = 0;
12288                 } else break;
12289             }
12290             boards[0][y][x] = EmptySquare;
12291             DrawPosition(FALSE, boards[0]);
12292         }
12293         break;
12294
12295       case PromotePiece:
12296         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12297            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12298             selection = (ChessSquare) (PROMOTED piece);
12299         } else if(piece == EmptySquare) selection = WhiteSilver;
12300         else selection = (ChessSquare)((int)piece - 1);
12301         goto defaultlabel;
12302
12303       case DemotePiece:
12304         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12305            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12306             selection = (ChessSquare) (DEMOTED piece);
12307         } else if(piece == EmptySquare) selection = BlackSilver;
12308         else selection = (ChessSquare)((int)piece + 1);
12309         goto defaultlabel;
12310
12311       case WhiteQueen:
12312       case BlackQueen:
12313         if(gameInfo.variant == VariantShatranj ||
12314            gameInfo.variant == VariantXiangqi  ||
12315            gameInfo.variant == VariantCourier  ||
12316            gameInfo.variant == VariantMakruk     )
12317             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12318         goto defaultlabel;
12319
12320       case WhiteKing:
12321       case BlackKing:
12322         if(gameInfo.variant == VariantXiangqi)
12323             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12324         if(gameInfo.variant == VariantKnightmate)
12325             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12326       default:
12327         defaultlabel:
12328         if (gameMode == IcsExamining) {
12329             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12330             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12331                      PieceToChar(selection), AAA + x, ONE + y);
12332             SendToICS(buf);
12333         } else {
12334             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12335                 int n;
12336                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12337                     n = PieceToNumber(selection - BlackPawn);
12338                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12339                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12340                     boards[0][BOARD_HEIGHT-1-n][1]++;
12341                 } else
12342                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12343                     n = PieceToNumber(selection);
12344                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12345                     boards[0][n][BOARD_WIDTH-1] = selection;
12346                     boards[0][n][BOARD_WIDTH-2]++;
12347                 }
12348             } else
12349             boards[0][y][x] = selection;
12350             DrawPosition(TRUE, boards[0]);
12351         }
12352         break;
12353     }
12354 }
12355
12356
12357 void
12358 DropMenuEvent(selection, x, y)
12359      ChessSquare selection;
12360      int x, y;
12361 {
12362     ChessMove moveType;
12363
12364     switch (gameMode) {
12365       case IcsPlayingWhite:
12366       case MachinePlaysBlack:
12367         if (!WhiteOnMove(currentMove)) {
12368             DisplayMoveError(_("It is Black's turn"));
12369             return;
12370         }
12371         moveType = WhiteDrop;
12372         break;
12373       case IcsPlayingBlack:
12374       case MachinePlaysWhite:
12375         if (WhiteOnMove(currentMove)) {
12376             DisplayMoveError(_("It is White's turn"));
12377             return;
12378         }
12379         moveType = BlackDrop;
12380         break;
12381       case EditGame:
12382         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12383         break;
12384       default:
12385         return;
12386     }
12387
12388     if (moveType == BlackDrop && selection < BlackPawn) {
12389       selection = (ChessSquare) ((int) selection
12390                                  + (int) BlackPawn - (int) WhitePawn);
12391     }
12392     if (boards[currentMove][y][x] != EmptySquare) {
12393         DisplayMoveError(_("That square is occupied"));
12394         return;
12395     }
12396
12397     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12398 }
12399
12400 void
12401 AcceptEvent()
12402 {
12403     /* Accept a pending offer of any kind from opponent */
12404
12405     if (appData.icsActive) {
12406         SendToICS(ics_prefix);
12407         SendToICS("accept\n");
12408     } else if (cmailMsgLoaded) {
12409         if (currentMove == cmailOldMove &&
12410             commentList[cmailOldMove] != NULL &&
12411             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12412                    "Black offers a draw" : "White offers a draw")) {
12413             TruncateGame();
12414             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12415             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12416         } else {
12417             DisplayError(_("There is no pending offer on this move"), 0);
12418             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12419         }
12420     } else {
12421         /* Not used for offers from chess program */
12422     }
12423 }
12424
12425 void
12426 DeclineEvent()
12427 {
12428     /* Decline a pending offer of any kind from opponent */
12429
12430     if (appData.icsActive) {
12431         SendToICS(ics_prefix);
12432         SendToICS("decline\n");
12433     } else if (cmailMsgLoaded) {
12434         if (currentMove == cmailOldMove &&
12435             commentList[cmailOldMove] != NULL &&
12436             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12437                    "Black offers a draw" : "White offers a draw")) {
12438 #ifdef NOTDEF
12439             AppendComment(cmailOldMove, "Draw declined", TRUE);
12440             DisplayComment(cmailOldMove - 1, "Draw declined");
12441 #endif /*NOTDEF*/
12442         } else {
12443             DisplayError(_("There is no pending offer on this move"), 0);
12444         }
12445     } else {
12446         /* Not used for offers from chess program */
12447     }
12448 }
12449
12450 void
12451 RematchEvent()
12452 {
12453     /* Issue ICS rematch command */
12454     if (appData.icsActive) {
12455         SendToICS(ics_prefix);
12456         SendToICS("rematch\n");
12457     }
12458 }
12459
12460 void
12461 CallFlagEvent()
12462 {
12463     /* Call your opponent's flag (claim a win on time) */
12464     if (appData.icsActive) {
12465         SendToICS(ics_prefix);
12466         SendToICS("flag\n");
12467     } else {
12468         switch (gameMode) {
12469           default:
12470             return;
12471           case MachinePlaysWhite:
12472             if (whiteFlag) {
12473                 if (blackFlag)
12474                   GameEnds(GameIsDrawn, "Both players ran out of time",
12475                            GE_PLAYER);
12476                 else
12477                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12478             } else {
12479                 DisplayError(_("Your opponent is not out of time"), 0);
12480             }
12481             break;
12482           case MachinePlaysBlack:
12483             if (blackFlag) {
12484                 if (whiteFlag)
12485                   GameEnds(GameIsDrawn, "Both players ran out of time",
12486                            GE_PLAYER);
12487                 else
12488                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12489             } else {
12490                 DisplayError(_("Your opponent is not out of time"), 0);
12491             }
12492             break;
12493         }
12494     }
12495 }
12496
12497 void
12498 DrawEvent()
12499 {
12500     /* Offer draw or accept pending draw offer from opponent */
12501
12502     if (appData.icsActive) {
12503         /* Note: tournament rules require draw offers to be
12504            made after you make your move but before you punch
12505            your clock.  Currently ICS doesn't let you do that;
12506            instead, you immediately punch your clock after making
12507            a move, but you can offer a draw at any time. */
12508
12509         SendToICS(ics_prefix);
12510         SendToICS("draw\n");
12511         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12512     } else if (cmailMsgLoaded) {
12513         if (currentMove == cmailOldMove &&
12514             commentList[cmailOldMove] != NULL &&
12515             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12516                    "Black offers a draw" : "White offers a draw")) {
12517             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12518             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12519         } else if (currentMove == cmailOldMove + 1) {
12520             char *offer = WhiteOnMove(cmailOldMove) ?
12521               "White offers a draw" : "Black offers a draw";
12522             AppendComment(currentMove, offer, TRUE);
12523             DisplayComment(currentMove - 1, offer);
12524             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12525         } else {
12526             DisplayError(_("You must make your move before offering a draw"), 0);
12527             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12528         }
12529     } else if (first.offeredDraw) {
12530         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12531     } else {
12532         if (first.sendDrawOffers) {
12533             SendToProgram("draw\n", &first);
12534             userOfferedDraw = TRUE;
12535         }
12536     }
12537 }
12538
12539 void
12540 AdjournEvent()
12541 {
12542     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12543
12544     if (appData.icsActive) {
12545         SendToICS(ics_prefix);
12546         SendToICS("adjourn\n");
12547     } else {
12548         /* Currently GNU Chess doesn't offer or accept Adjourns */
12549     }
12550 }
12551
12552
12553 void
12554 AbortEvent()
12555 {
12556     /* Offer Abort or accept pending Abort offer from opponent */
12557
12558     if (appData.icsActive) {
12559         SendToICS(ics_prefix);
12560         SendToICS("abort\n");
12561     } else {
12562         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12563     }
12564 }
12565
12566 void
12567 ResignEvent()
12568 {
12569     /* Resign.  You can do this even if it's not your turn. */
12570
12571     if (appData.icsActive) {
12572         SendToICS(ics_prefix);
12573         SendToICS("resign\n");
12574     } else {
12575         switch (gameMode) {
12576           case MachinePlaysWhite:
12577             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12578             break;
12579           case MachinePlaysBlack:
12580             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12581             break;
12582           case EditGame:
12583             if (cmailMsgLoaded) {
12584                 TruncateGame();
12585                 if (WhiteOnMove(cmailOldMove)) {
12586                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12587                 } else {
12588                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12589                 }
12590                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12591             }
12592             break;
12593           default:
12594             break;
12595         }
12596     }
12597 }
12598
12599
12600 void
12601 StopObservingEvent()
12602 {
12603     /* Stop observing current games */
12604     SendToICS(ics_prefix);
12605     SendToICS("unobserve\n");
12606 }
12607
12608 void
12609 StopExaminingEvent()
12610 {
12611     /* Stop observing current game */
12612     SendToICS(ics_prefix);
12613     SendToICS("unexamine\n");
12614 }
12615
12616 void
12617 ForwardInner(target)
12618      int target;
12619 {
12620     int limit;
12621
12622     if (appData.debugMode)
12623         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
12624                 target, currentMove, forwardMostMove);
12625
12626     if (gameMode == EditPosition)
12627       return;
12628
12629     if (gameMode == PlayFromGameFile && !pausing)
12630       PauseEvent();
12631
12632     if (gameMode == IcsExamining && pausing)
12633       limit = pauseExamForwardMostMove;
12634     else
12635       limit = forwardMostMove;
12636
12637     if (target > limit) target = limit;
12638
12639     if (target > 0 && moveList[target - 1][0]) {
12640         int fromX, fromY, toX, toY;
12641         toX = moveList[target - 1][2] - AAA;
12642         toY = moveList[target - 1][3] - ONE;
12643         if (moveList[target - 1][1] == '@') {
12644             if (appData.highlightLastMove) {
12645                 SetHighlights(-1, -1, toX, toY);
12646             }
12647         } else {
12648             fromX = moveList[target - 1][0] - AAA;
12649             fromY = moveList[target - 1][1] - ONE;
12650             if (target == currentMove + 1) {
12651                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
12652             }
12653             if (appData.highlightLastMove) {
12654                 SetHighlights(fromX, fromY, toX, toY);
12655             }
12656         }
12657     }
12658     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12659         gameMode == Training || gameMode == PlayFromGameFile ||
12660         gameMode == AnalyzeFile) {
12661         while (currentMove < target) {
12662             SendMoveToProgram(currentMove++, &first);
12663         }
12664     } else {
12665         currentMove = target;
12666     }
12667
12668     if (gameMode == EditGame || gameMode == EndOfGame) {
12669         whiteTimeRemaining = timeRemaining[0][currentMove];
12670         blackTimeRemaining = timeRemaining[1][currentMove];
12671     }
12672     DisplayBothClocks();
12673     DisplayMove(currentMove - 1);
12674     DrawPosition(FALSE, boards[currentMove]);
12675     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12676     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
12677         DisplayComment(currentMove - 1, commentList[currentMove]);
12678     }
12679 }
12680
12681
12682 void
12683 ForwardEvent()
12684 {
12685     if (gameMode == IcsExamining && !pausing) {
12686         SendToICS(ics_prefix);
12687         SendToICS("forward\n");
12688     } else {
12689         ForwardInner(currentMove + 1);
12690     }
12691 }
12692
12693 void
12694 ToEndEvent()
12695 {
12696     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12697         /* to optimze, we temporarily turn off analysis mode while we feed
12698          * the remaining moves to the engine. Otherwise we get analysis output
12699          * after each move.
12700          */
12701         if (first.analysisSupport) {
12702           SendToProgram("exit\nforce\n", &first);
12703           first.analyzing = FALSE;
12704         }
12705     }
12706
12707     if (gameMode == IcsExamining && !pausing) {
12708         SendToICS(ics_prefix);
12709         SendToICS("forward 999999\n");
12710     } else {
12711         ForwardInner(forwardMostMove);
12712     }
12713
12714     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12715         /* we have fed all the moves, so reactivate analysis mode */
12716         SendToProgram("analyze\n", &first);
12717         first.analyzing = TRUE;
12718         /*first.maybeThinking = TRUE;*/
12719         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12720     }
12721 }
12722
12723 void
12724 BackwardInner(target)
12725      int target;
12726 {
12727     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
12728
12729     if (appData.debugMode)
12730         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
12731                 target, currentMove, forwardMostMove);
12732
12733     if (gameMode == EditPosition) return;
12734     if (currentMove <= backwardMostMove) {
12735         ClearHighlights();
12736         DrawPosition(full_redraw, boards[currentMove]);
12737         return;
12738     }
12739     if (gameMode == PlayFromGameFile && !pausing)
12740       PauseEvent();
12741
12742     if (moveList[target][0]) {
12743         int fromX, fromY, toX, toY;
12744         toX = moveList[target][2] - AAA;
12745         toY = moveList[target][3] - ONE;
12746         if (moveList[target][1] == '@') {
12747             if (appData.highlightLastMove) {
12748                 SetHighlights(-1, -1, toX, toY);
12749             }
12750         } else {
12751             fromX = moveList[target][0] - AAA;
12752             fromY = moveList[target][1] - ONE;
12753             if (target == currentMove - 1) {
12754                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
12755             }
12756             if (appData.highlightLastMove) {
12757                 SetHighlights(fromX, fromY, toX, toY);
12758             }
12759         }
12760     }
12761     if (gameMode == EditGame || gameMode==AnalyzeMode ||
12762         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
12763         while (currentMove > target) {
12764             SendToProgram("undo\n", &first);
12765             currentMove--;
12766         }
12767     } else {
12768         currentMove = target;
12769     }
12770
12771     if (gameMode == EditGame || gameMode == EndOfGame) {
12772         whiteTimeRemaining = timeRemaining[0][currentMove];
12773         blackTimeRemaining = timeRemaining[1][currentMove];
12774     }
12775     DisplayBothClocks();
12776     DisplayMove(currentMove - 1);
12777     DrawPosition(full_redraw, boards[currentMove]);
12778     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
12779     // [HGM] PV info: routine tests if comment empty
12780     DisplayComment(currentMove - 1, commentList[currentMove]);
12781 }
12782
12783 void
12784 BackwardEvent()
12785 {
12786     if (gameMode == IcsExamining && !pausing) {
12787         SendToICS(ics_prefix);
12788         SendToICS("backward\n");
12789     } else {
12790         BackwardInner(currentMove - 1);
12791     }
12792 }
12793
12794 void
12795 ToStartEvent()
12796 {
12797     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12798         /* to optimize, we temporarily turn off analysis mode while we undo
12799          * all the moves. Otherwise we get analysis output after each undo.
12800          */
12801         if (first.analysisSupport) {
12802           SendToProgram("exit\nforce\n", &first);
12803           first.analyzing = FALSE;
12804         }
12805     }
12806
12807     if (gameMode == IcsExamining && !pausing) {
12808         SendToICS(ics_prefix);
12809         SendToICS("backward 999999\n");
12810     } else {
12811         BackwardInner(backwardMostMove);
12812     }
12813
12814     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
12815         /* we have fed all the moves, so reactivate analysis mode */
12816         SendToProgram("analyze\n", &first);
12817         first.analyzing = TRUE;
12818         /*first.maybeThinking = TRUE;*/
12819         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12820     }
12821 }
12822
12823 void
12824 ToNrEvent(int to)
12825 {
12826   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
12827   if (to >= forwardMostMove) to = forwardMostMove;
12828   if (to <= backwardMostMove) to = backwardMostMove;
12829   if (to < currentMove) {
12830     BackwardInner(to);
12831   } else {
12832     ForwardInner(to);
12833   }
12834 }
12835
12836 void
12837 RevertEvent(Boolean annotate)
12838 {
12839     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
12840         return;
12841     }
12842     if (gameMode != IcsExamining) {
12843         DisplayError(_("You are not examining a game"), 0);
12844         return;
12845     }
12846     if (pausing) {
12847         DisplayError(_("You can't revert while pausing"), 0);
12848         return;
12849     }
12850     SendToICS(ics_prefix);
12851     SendToICS("revert\n");
12852 }
12853
12854 void
12855 RetractMoveEvent()
12856 {
12857     switch (gameMode) {
12858       case MachinePlaysWhite:
12859       case MachinePlaysBlack:
12860         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12861             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12862             return;
12863         }
12864         if (forwardMostMove < 2) return;
12865         currentMove = forwardMostMove = forwardMostMove - 2;
12866         whiteTimeRemaining = timeRemaining[0][currentMove];
12867         blackTimeRemaining = timeRemaining[1][currentMove];
12868         DisplayBothClocks();
12869         DisplayMove(currentMove - 1);
12870         ClearHighlights();/*!! could figure this out*/
12871         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
12872         SendToProgram("remove\n", &first);
12873         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
12874         break;
12875
12876       case BeginningOfGame:
12877       default:
12878         break;
12879
12880       case IcsPlayingWhite:
12881       case IcsPlayingBlack:
12882         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
12883             SendToICS(ics_prefix);
12884             SendToICS("takeback 2\n");
12885         } else {
12886             SendToICS(ics_prefix);
12887             SendToICS("takeback 1\n");
12888         }
12889         break;
12890     }
12891 }
12892
12893 void
12894 MoveNowEvent()
12895 {
12896     ChessProgramState *cps;
12897
12898     switch (gameMode) {
12899       case MachinePlaysWhite:
12900         if (!WhiteOnMove(forwardMostMove)) {
12901             DisplayError(_("It is your turn"), 0);
12902             return;
12903         }
12904         cps = &first;
12905         break;
12906       case MachinePlaysBlack:
12907         if (WhiteOnMove(forwardMostMove)) {
12908             DisplayError(_("It is your turn"), 0);
12909             return;
12910         }
12911         cps = &first;
12912         break;
12913       case TwoMachinesPlay:
12914         if (WhiteOnMove(forwardMostMove) ==
12915             (first.twoMachinesColor[0] == 'w')) {
12916             cps = &first;
12917         } else {
12918             cps = &second;
12919         }
12920         break;
12921       case BeginningOfGame:
12922       default:
12923         return;
12924     }
12925     SendToProgram("?\n", cps);
12926 }
12927
12928 void
12929 TruncateGameEvent()
12930 {
12931     EditGameEvent();
12932     if (gameMode != EditGame) return;
12933     TruncateGame();
12934 }
12935
12936 void
12937 TruncateGame()
12938 {
12939     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
12940     if (forwardMostMove > currentMove) {
12941         if (gameInfo.resultDetails != NULL) {
12942             free(gameInfo.resultDetails);
12943             gameInfo.resultDetails = NULL;
12944             gameInfo.result = GameUnfinished;
12945         }
12946         forwardMostMove = currentMove;
12947         HistorySet(parseList, backwardMostMove, forwardMostMove,
12948                    currentMove-1);
12949     }
12950 }
12951
12952 void
12953 HintEvent()
12954 {
12955     if (appData.noChessProgram) return;
12956     switch (gameMode) {
12957       case MachinePlaysWhite:
12958         if (WhiteOnMove(forwardMostMove)) {
12959             DisplayError(_("Wait until your turn"), 0);
12960             return;
12961         }
12962         break;
12963       case BeginningOfGame:
12964       case MachinePlaysBlack:
12965         if (!WhiteOnMove(forwardMostMove)) {
12966             DisplayError(_("Wait until your turn"), 0);
12967             return;
12968         }
12969         break;
12970       default:
12971         DisplayError(_("No hint available"), 0);
12972         return;
12973     }
12974     SendToProgram("hint\n", &first);
12975     hintRequested = TRUE;
12976 }
12977
12978 void
12979 BookEvent()
12980 {
12981     if (appData.noChessProgram) return;
12982     switch (gameMode) {
12983       case MachinePlaysWhite:
12984         if (WhiteOnMove(forwardMostMove)) {
12985             DisplayError(_("Wait until your turn"), 0);
12986             return;
12987         }
12988         break;
12989       case BeginningOfGame:
12990       case MachinePlaysBlack:
12991         if (!WhiteOnMove(forwardMostMove)) {
12992             DisplayError(_("Wait until your turn"), 0);
12993             return;
12994         }
12995         break;
12996       case EditPosition:
12997         EditPositionDone(TRUE);
12998         break;
12999       case TwoMachinesPlay:
13000         return;
13001       default:
13002         break;
13003     }
13004     SendToProgram("bk\n", &first);
13005     bookOutput[0] = NULLCHAR;
13006     bookRequested = TRUE;
13007 }
13008
13009 void
13010 AboutGameEvent()
13011 {
13012     char *tags = PGNTags(&gameInfo);
13013     TagsPopUp(tags, CmailMsg());
13014     free(tags);
13015 }
13016
13017 /* end button procedures */
13018
13019 void
13020 PrintPosition(fp, move)
13021      FILE *fp;
13022      int move;
13023 {
13024     int i, j;
13025
13026     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13027         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13028             char c = PieceToChar(boards[move][i][j]);
13029             fputc(c == 'x' ? '.' : c, fp);
13030             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13031         }
13032     }
13033     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13034       fprintf(fp, "white to play\n");
13035     else
13036       fprintf(fp, "black to play\n");
13037 }
13038
13039 void
13040 PrintOpponents(fp)
13041      FILE *fp;
13042 {
13043     if (gameInfo.white != NULL) {
13044         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13045     } else {
13046         fprintf(fp, "\n");
13047     }
13048 }
13049
13050 /* Find last component of program's own name, using some heuristics */
13051 void
13052 TidyProgramName(prog, host, buf)
13053      char *prog, *host, buf[MSG_SIZ];
13054 {
13055     char *p, *q;
13056     int local = (strcmp(host, "localhost") == 0);
13057     while (!local && (p = strchr(prog, ';')) != NULL) {
13058         p++;
13059         while (*p == ' ') p++;
13060         prog = p;
13061     }
13062     if (*prog == '"' || *prog == '\'') {
13063         q = strchr(prog + 1, *prog);
13064     } else {
13065         q = strchr(prog, ' ');
13066     }
13067     if (q == NULL) q = prog + strlen(prog);
13068     p = q;
13069     while (p >= prog && *p != '/' && *p != '\\') p--;
13070     p++;
13071     if(p == prog && *p == '"') p++;
13072     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13073     memcpy(buf, p, q - p);
13074     buf[q - p] = NULLCHAR;
13075     if (!local) {
13076         strcat(buf, "@");
13077         strcat(buf, host);
13078     }
13079 }
13080
13081 char *
13082 TimeControlTagValue()
13083 {
13084     char buf[MSG_SIZ];
13085     if (!appData.clockMode) {
13086       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13087     } else if (movesPerSession > 0) {
13088       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13089     } else if (timeIncrement == 0) {
13090       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13091     } else {
13092       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13093     }
13094     return StrSave(buf);
13095 }
13096
13097 void
13098 SetGameInfo()
13099 {
13100     /* This routine is used only for certain modes */
13101     VariantClass v = gameInfo.variant;
13102     ChessMove r = GameUnfinished;
13103     char *p = NULL;
13104
13105     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13106         r = gameInfo.result;
13107         p = gameInfo.resultDetails;
13108         gameInfo.resultDetails = NULL;
13109     }
13110     ClearGameInfo(&gameInfo);
13111     gameInfo.variant = v;
13112
13113     switch (gameMode) {
13114       case MachinePlaysWhite:
13115         gameInfo.event = StrSave( appData.pgnEventHeader );
13116         gameInfo.site = StrSave(HostName());
13117         gameInfo.date = PGNDate();
13118         gameInfo.round = StrSave("-");
13119         gameInfo.white = StrSave(first.tidy);
13120         gameInfo.black = StrSave(UserName());
13121         gameInfo.timeControl = TimeControlTagValue();
13122         break;
13123
13124       case MachinePlaysBlack:
13125         gameInfo.event = StrSave( appData.pgnEventHeader );
13126         gameInfo.site = StrSave(HostName());
13127         gameInfo.date = PGNDate();
13128         gameInfo.round = StrSave("-");
13129         gameInfo.white = StrSave(UserName());
13130         gameInfo.black = StrSave(first.tidy);
13131         gameInfo.timeControl = TimeControlTagValue();
13132         break;
13133
13134       case TwoMachinesPlay:
13135         gameInfo.event = StrSave( appData.pgnEventHeader );
13136         gameInfo.site = StrSave(HostName());
13137         gameInfo.date = PGNDate();
13138         if (matchGame > 0) {
13139             char buf[MSG_SIZ];
13140             snprintf(buf, MSG_SIZ, "%d", matchGame);
13141             gameInfo.round = StrSave(buf);
13142         } else {
13143             gameInfo.round = StrSave("-");
13144         }
13145         if (first.twoMachinesColor[0] == 'w') {
13146             gameInfo.white = StrSave(first.tidy);
13147             gameInfo.black = StrSave(second.tidy);
13148         } else {
13149             gameInfo.white = StrSave(second.tidy);
13150             gameInfo.black = StrSave(first.tidy);
13151         }
13152         gameInfo.timeControl = TimeControlTagValue();
13153         break;
13154
13155       case EditGame:
13156         gameInfo.event = StrSave("Edited game");
13157         gameInfo.site = StrSave(HostName());
13158         gameInfo.date = PGNDate();
13159         gameInfo.round = StrSave("-");
13160         gameInfo.white = StrSave("-");
13161         gameInfo.black = StrSave("-");
13162         gameInfo.result = r;
13163         gameInfo.resultDetails = p;
13164         break;
13165
13166       case EditPosition:
13167         gameInfo.event = StrSave("Edited position");
13168         gameInfo.site = StrSave(HostName());
13169         gameInfo.date = PGNDate();
13170         gameInfo.round = StrSave("-");
13171         gameInfo.white = StrSave("-");
13172         gameInfo.black = StrSave("-");
13173         break;
13174
13175       case IcsPlayingWhite:
13176       case IcsPlayingBlack:
13177       case IcsObserving:
13178       case IcsExamining:
13179         break;
13180
13181       case PlayFromGameFile:
13182         gameInfo.event = StrSave("Game from non-PGN file");
13183         gameInfo.site = StrSave(HostName());
13184         gameInfo.date = PGNDate();
13185         gameInfo.round = StrSave("-");
13186         gameInfo.white = StrSave("?");
13187         gameInfo.black = StrSave("?");
13188         break;
13189
13190       default:
13191         break;
13192     }
13193 }
13194
13195 void
13196 ReplaceComment(index, text)
13197      int index;
13198      char *text;
13199 {
13200     int len;
13201
13202     while (*text == '\n') text++;
13203     len = strlen(text);
13204     while (len > 0 && text[len - 1] == '\n') len--;
13205
13206     if (commentList[index] != NULL)
13207       free(commentList[index]);
13208
13209     if (len == 0) {
13210         commentList[index] = NULL;
13211         return;
13212     }
13213   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13214       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13215       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13216     commentList[index] = (char *) malloc(len + 2);
13217     strncpy(commentList[index], text, len);
13218     commentList[index][len] = '\n';
13219     commentList[index][len + 1] = NULLCHAR;
13220   } else {
13221     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13222     char *p;
13223     commentList[index] = (char *) malloc(len + 7);
13224     safeStrCpy(commentList[index], "{\n", 3);
13225     safeStrCpy(commentList[index]+2, text, len+1);
13226     commentList[index][len+2] = NULLCHAR;
13227     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13228     strcat(commentList[index], "\n}\n");
13229   }
13230 }
13231
13232 void
13233 CrushCRs(text)
13234      char *text;
13235 {
13236   char *p = text;
13237   char *q = text;
13238   char ch;
13239
13240   do {
13241     ch = *p++;
13242     if (ch == '\r') continue;
13243     *q++ = ch;
13244   } while (ch != '\0');
13245 }
13246
13247 void
13248 AppendComment(index, text, addBraces)
13249      int index;
13250      char *text;
13251      Boolean addBraces; // [HGM] braces: tells if we should add {}
13252 {
13253     int oldlen, len;
13254     char *old;
13255
13256 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13257     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13258
13259     CrushCRs(text);
13260     while (*text == '\n') text++;
13261     len = strlen(text);
13262     while (len > 0 && text[len - 1] == '\n') len--;
13263
13264     if (len == 0) return;
13265
13266     if (commentList[index] != NULL) {
13267         old = commentList[index];
13268         oldlen = strlen(old);
13269         while(commentList[index][oldlen-1] ==  '\n')
13270           commentList[index][--oldlen] = NULLCHAR;
13271         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13272         safeStrCpy(commentList[index], old, oldlen);
13273         free(old);
13274         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13275         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
13276           if(addBraces) addBraces = FALSE; else { text++; len--; }
13277           while (*text == '\n') { text++; len--; }
13278           commentList[index][--oldlen] = NULLCHAR;
13279       }
13280         if(addBraces) strcat(commentList[index], "\n{\n");
13281         else          strcat(commentList[index], "\n");
13282         strcat(commentList[index], text);
13283         if(addBraces) strcat(commentList[index], "\n}\n");
13284         else          strcat(commentList[index], "\n");
13285     } else {
13286         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13287         if(addBraces)
13288           safeStrCpy(commentList[index], "{\n", sizeof(commentList[index])/sizeof(commentList[index][0]));
13289         else commentList[index][0] = NULLCHAR;
13290         strcat(commentList[index], text);
13291         strcat(commentList[index], "\n");
13292         if(addBraces) strcat(commentList[index], "}\n");
13293     }
13294 }
13295
13296 static char * FindStr( char * text, char * sub_text )
13297 {
13298     char * result = strstr( text, sub_text );
13299
13300     if( result != NULL ) {
13301         result += strlen( sub_text );
13302     }
13303
13304     return result;
13305 }
13306
13307 /* [AS] Try to extract PV info from PGN comment */
13308 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13309 char *GetInfoFromComment( int index, char * text )
13310 {
13311     char * sep = text;
13312
13313     if( text != NULL && index > 0 ) {
13314         int score = 0;
13315         int depth = 0;
13316         int time = -1, sec = 0, deci;
13317         char * s_eval = FindStr( text, "[%eval " );
13318         char * s_emt = FindStr( text, "[%emt " );
13319
13320         if( s_eval != NULL || s_emt != NULL ) {
13321             /* New style */
13322             char delim;
13323
13324             if( s_eval != NULL ) {
13325                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13326                     return text;
13327                 }
13328
13329                 if( delim != ']' ) {
13330                     return text;
13331                 }
13332             }
13333
13334             if( s_emt != NULL ) {
13335             }
13336                 return text;
13337         }
13338         else {
13339             /* We expect something like: [+|-]nnn.nn/dd */
13340             int score_lo = 0;
13341
13342             if(*text != '{') return text; // [HGM] braces: must be normal comment
13343
13344             sep = strchr( text, '/' );
13345             if( sep == NULL || sep < (text+4) ) {
13346                 return text;
13347             }
13348
13349             time = -1; sec = -1; deci = -1;
13350             if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13351                 sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13352                 sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13353                 sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13354                 return text;
13355             }
13356
13357             if( score_lo < 0 || score_lo >= 100 ) {
13358                 return text;
13359             }
13360
13361             if(sec >= 0) time = 600*time + 10*sec; else
13362             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13363
13364             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13365
13366             /* [HGM] PV time: now locate end of PV info */
13367             while( *++sep >= '0' && *sep <= '9'); // strip depth
13368             if(time >= 0)
13369             while( *++sep >= '0' && *sep <= '9'); // strip time
13370             if(sec >= 0)
13371             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13372             if(deci >= 0)
13373             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13374             while(*sep == ' ') sep++;
13375         }
13376
13377         if( depth <= 0 ) {
13378             return text;
13379         }
13380
13381         if( time < 0 ) {
13382             time = -1;
13383         }
13384
13385         pvInfoList[index-1].depth = depth;
13386         pvInfoList[index-1].score = score;
13387         pvInfoList[index-1].time  = 10*time; // centi-sec
13388         if(*sep == '}') *sep = 0; else *--sep = '{';
13389     }
13390     return sep;
13391 }
13392
13393 void
13394 SendToProgram(message, cps)
13395      char *message;
13396      ChessProgramState *cps;
13397 {
13398     int count, outCount, error;
13399     char buf[MSG_SIZ];
13400
13401     if (cps->pr == NULL) return;
13402     Attention(cps);
13403
13404     if (appData.debugMode) {
13405         TimeMark now;
13406         GetTimeMark(&now);
13407         fprintf(debugFP, "%ld >%-6s: %s",
13408                 SubtractTimeMarks(&now, &programStartTime),
13409                 cps->which, message);
13410     }
13411
13412     count = strlen(message);
13413     outCount = OutputToProcess(cps->pr, message, count, &error);
13414     if (outCount < count && !exiting
13415                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13416       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), cps->which);
13417         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13418             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13419                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13420                 snprintf(buf, MSG_SIZ, "%s program exits in draw position (%s)", cps->which, cps->program);
13421             } else {
13422                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13423             }
13424             gameInfo.resultDetails = StrSave(buf);
13425         }
13426         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13427     }
13428 }
13429
13430 void
13431 ReceiveFromProgram(isr, closure, message, count, error)
13432      InputSourceRef isr;
13433      VOIDSTAR closure;
13434      char *message;
13435      int count;
13436      int error;
13437 {
13438     char *end_str;
13439     char buf[MSG_SIZ];
13440     ChessProgramState *cps = (ChessProgramState *)closure;
13441
13442     if (isr != cps->isr) return; /* Killed intentionally */
13443     if (count <= 0) {
13444         if (count == 0) {
13445             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13446                     cps->which, cps->program);
13447         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13448                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13449                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13450                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), cps->which, cps->program);
13451                 } else {
13452                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13453                 }
13454                 gameInfo.resultDetails = StrSave(buf);
13455             }
13456             RemoveInputSource(cps->isr);
13457             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13458         } else {
13459             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13460                     cps->which, cps->program);
13461             RemoveInputSource(cps->isr);
13462
13463             /* [AS] Program is misbehaving badly... kill it */
13464             if( count == -2 ) {
13465                 DestroyChildProcess( cps->pr, 9 );
13466                 cps->pr = NoProc;
13467             }
13468
13469             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13470         }
13471         return;
13472     }
13473
13474     if ((end_str = strchr(message, '\r')) != NULL)
13475       *end_str = NULLCHAR;
13476     if ((end_str = strchr(message, '\n')) != NULL)
13477       *end_str = NULLCHAR;
13478
13479     if (appData.debugMode) {
13480         TimeMark now; int print = 1;
13481         char *quote = ""; char c; int i;
13482
13483         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13484                 char start = message[0];
13485                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13486                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13487                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13488                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13489                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13490                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13491                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13492                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13493                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13494                     print = (appData.engineComments >= 2);
13495                 }
13496                 message[0] = start; // restore original message
13497         }
13498         if(print) {
13499                 GetTimeMark(&now);
13500                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13501                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13502                         quote,
13503                         message);
13504         }
13505     }
13506
13507     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13508     if (appData.icsEngineAnalyze) {
13509         if (strstr(message, "whisper") != NULL ||
13510              strstr(message, "kibitz") != NULL ||
13511             strstr(message, "tellics") != NULL) return;
13512     }
13513
13514     HandleMachineMove(message, cps);
13515 }
13516
13517
13518 void
13519 SendTimeControl(cps, mps, tc, inc, sd, st)
13520      ChessProgramState *cps;
13521      int mps, inc, sd, st;
13522      long tc;
13523 {
13524     char buf[MSG_SIZ];
13525     int seconds;
13526
13527     if( timeControl_2 > 0 ) {
13528         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13529             tc = timeControl_2;
13530         }
13531     }
13532     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13533     inc /= cps->timeOdds;
13534     st  /= cps->timeOdds;
13535
13536     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13537
13538     if (st > 0) {
13539       /* Set exact time per move, normally using st command */
13540       if (cps->stKludge) {
13541         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13542         seconds = st % 60;
13543         if (seconds == 0) {
13544           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13545         } else {
13546           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13547         }
13548       } else {
13549         snprintf(buf, MSG_SIZ, "st %d\n", st);
13550       }
13551     } else {
13552       /* Set conventional or incremental time control, using level command */
13553       if (seconds == 0) {
13554         /* Note old gnuchess bug -- minutes:seconds used to not work.
13555            Fixed in later versions, but still avoid :seconds
13556            when seconds is 0. */
13557         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000);
13558       } else {
13559         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13560                  seconds, inc/1000);
13561       }
13562     }
13563     SendToProgram(buf, cps);
13564
13565     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13566     /* Orthogonally, limit search to given depth */
13567     if (sd > 0) {
13568       if (cps->sdKludge) {
13569         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13570       } else {
13571         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13572       }
13573       SendToProgram(buf, cps);
13574     }
13575
13576     if(cps->nps > 0) { /* [HGM] nps */
13577         if(cps->supportsNPS == FALSE)
13578           cps->nps = -1; // don't use if engine explicitly says not supported!
13579         else {
13580           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13581           SendToProgram(buf, cps);
13582         }
13583     }
13584 }
13585
13586 ChessProgramState *WhitePlayer()
13587 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13588 {
13589     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13590        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13591         return &second;
13592     return &first;
13593 }
13594
13595 void
13596 SendTimeRemaining(cps, machineWhite)
13597      ChessProgramState *cps;
13598      int /*boolean*/ machineWhite;
13599 {
13600     char message[MSG_SIZ];
13601     long time, otime;
13602
13603     /* Note: this routine must be called when the clocks are stopped
13604        or when they have *just* been set or switched; otherwise
13605        it will be off by the time since the current tick started.
13606     */
13607     if (machineWhite) {
13608         time = whiteTimeRemaining / 10;
13609         otime = blackTimeRemaining / 10;
13610     } else {
13611         time = blackTimeRemaining / 10;
13612         otime = whiteTimeRemaining / 10;
13613     }
13614     /* [HGM] translate opponent's time by time-odds factor */
13615     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
13616     if (appData.debugMode) {
13617         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
13618     }
13619
13620     if (time <= 0) time = 1;
13621     if (otime <= 0) otime = 1;
13622
13623     snprintf(message, MSG_SIZ, "time %ld\n", time);
13624     SendToProgram(message, cps);
13625
13626     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
13627     SendToProgram(message, cps);
13628 }
13629
13630 int
13631 BoolFeature(p, name, loc, cps)
13632      char **p;
13633      char *name;
13634      int *loc;
13635      ChessProgramState *cps;
13636 {
13637   char buf[MSG_SIZ];
13638   int len = strlen(name);
13639   int val;
13640
13641   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13642     (*p) += len + 1;
13643     sscanf(*p, "%d", &val);
13644     *loc = (val != 0);
13645     while (**p && **p != ' ')
13646       (*p)++;
13647     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13648     SendToProgram(buf, cps);
13649     return TRUE;
13650   }
13651   return FALSE;
13652 }
13653
13654 int
13655 IntFeature(p, name, loc, cps)
13656      char **p;
13657      char *name;
13658      int *loc;
13659      ChessProgramState *cps;
13660 {
13661   char buf[MSG_SIZ];
13662   int len = strlen(name);
13663   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
13664     (*p) += len + 1;
13665     sscanf(*p, "%d", loc);
13666     while (**p && **p != ' ') (*p)++;
13667     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13668     SendToProgram(buf, cps);
13669     return TRUE;
13670   }
13671   return FALSE;
13672 }
13673
13674 int
13675 StringFeature(p, name, loc, cps)
13676      char **p;
13677      char *name;
13678      char loc[];
13679      ChessProgramState *cps;
13680 {
13681   char buf[MSG_SIZ];
13682   int len = strlen(name);
13683   if (strncmp((*p), name, len) == 0
13684       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
13685     (*p) += len + 2;
13686     sscanf(*p, "%[^\"]", loc);
13687     while (**p && **p != '\"') (*p)++;
13688     if (**p == '\"') (*p)++;
13689     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
13690     SendToProgram(buf, cps);
13691     return TRUE;
13692   }
13693   return FALSE;
13694 }
13695
13696 int
13697 ParseOption(Option *opt, ChessProgramState *cps)
13698 // [HGM] options: process the string that defines an engine option, and determine
13699 // name, type, default value, and allowed value range
13700 {
13701         char *p, *q, buf[MSG_SIZ];
13702         int n, min = (-1)<<31, max = 1<<31, def;
13703
13704         if(p = strstr(opt->name, " -spin ")) {
13705             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13706             if(max < min) max = min; // enforce consistency
13707             if(def < min) def = min;
13708             if(def > max) def = max;
13709             opt->value = def;
13710             opt->min = min;
13711             opt->max = max;
13712             opt->type = Spin;
13713         } else if((p = strstr(opt->name, " -slider "))) {
13714             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
13715             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
13716             if(max < min) max = min; // enforce consistency
13717             if(def < min) def = min;
13718             if(def > max) def = max;
13719             opt->value = def;
13720             opt->min = min;
13721             opt->max = max;
13722             opt->type = Spin; // Slider;
13723         } else if((p = strstr(opt->name, " -string "))) {
13724             opt->textValue = p+9;
13725             opt->type = TextBox;
13726         } else if((p = strstr(opt->name, " -file "))) {
13727             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13728             opt->textValue = p+7;
13729             opt->type = TextBox; // FileName;
13730         } else if((p = strstr(opt->name, " -path "))) {
13731             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
13732             opt->textValue = p+7;
13733             opt->type = TextBox; // PathName;
13734         } else if(p = strstr(opt->name, " -check ")) {
13735             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
13736             opt->value = (def != 0);
13737             opt->type = CheckBox;
13738         } else if(p = strstr(opt->name, " -combo ")) {
13739             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
13740             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
13741             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
13742             opt->value = n = 0;
13743             while(q = StrStr(q, " /// ")) {
13744                 n++; *q = 0;    // count choices, and null-terminate each of them
13745                 q += 5;
13746                 if(*q == '*') { // remember default, which is marked with * prefix
13747                     q++;
13748                     opt->value = n;
13749                 }
13750                 cps->comboList[cps->comboCnt++] = q;
13751             }
13752             cps->comboList[cps->comboCnt++] = NULL;
13753             opt->max = n + 1;
13754             opt->type = ComboBox;
13755         } else if(p = strstr(opt->name, " -button")) {
13756             opt->type = Button;
13757         } else if(p = strstr(opt->name, " -save")) {
13758             opt->type = SaveButton;
13759         } else return FALSE;
13760         *p = 0; // terminate option name
13761         // now look if the command-line options define a setting for this engine option.
13762         if(cps->optionSettings && cps->optionSettings[0])
13763             p = strstr(cps->optionSettings, opt->name); else p = NULL;
13764         if(p && (p == cps->optionSettings || p[-1] == ',')) {
13765           snprintf(buf, MSG_SIZ, "option %s", p);
13766                 if(p = strstr(buf, ",")) *p = 0;
13767                 strcat(buf, "\n");
13768                 SendToProgram(buf, cps);
13769         }
13770         return TRUE;
13771 }
13772
13773 void
13774 FeatureDone(cps, val)
13775      ChessProgramState* cps;
13776      int val;
13777 {
13778   DelayedEventCallback cb = GetDelayedEvent();
13779   if ((cb == InitBackEnd3 && cps == &first) ||
13780       (cb == TwoMachinesEventIfReady && cps == &second)) {
13781     CancelDelayedEvent();
13782     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
13783   }
13784   cps->initDone = val;
13785 }
13786
13787 /* Parse feature command from engine */
13788 void
13789 ParseFeatures(args, cps)
13790      char* args;
13791      ChessProgramState *cps;
13792 {
13793   char *p = args;
13794   char *q;
13795   int val;
13796   char buf[MSG_SIZ];
13797
13798   for (;;) {
13799     while (*p == ' ') p++;
13800     if (*p == NULLCHAR) return;
13801
13802     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
13803     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
13804     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
13805     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
13806     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
13807     if (BoolFeature(&p, "reuse", &val, cps)) {
13808       /* Engine can disable reuse, but can't enable it if user said no */
13809       if (!val) cps->reuse = FALSE;
13810       continue;
13811     }
13812     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
13813     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
13814       if (gameMode == TwoMachinesPlay) {
13815         DisplayTwoMachinesTitle();
13816       } else {
13817         DisplayTitle("");
13818       }
13819       continue;
13820     }
13821     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
13822     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
13823     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
13824     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
13825     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
13826     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
13827     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
13828     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
13829     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
13830     if (IntFeature(&p, "done", &val, cps)) {
13831       FeatureDone(cps, val);
13832       continue;
13833     }
13834     /* Added by Tord: */
13835     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
13836     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
13837     /* End of additions by Tord */
13838
13839     /* [HGM] added features: */
13840     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
13841     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
13842     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
13843     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
13844     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
13845     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
13846     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
13847         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
13848           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
13849             SendToProgram(buf, cps);
13850             continue;
13851         }
13852         if(cps->nrOptions >= MAX_OPTIONS) {
13853             cps->nrOptions--;
13854             snprintf(buf, MSG_SIZ, "%s engine has too many options\n", cps->which);
13855             DisplayError(buf, 0);
13856         }
13857         continue;
13858     }
13859     /* End of additions by HGM */
13860
13861     /* unknown feature: complain and skip */
13862     q = p;
13863     while (*q && *q != '=') q++;
13864     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
13865     SendToProgram(buf, cps);
13866     p = q;
13867     if (*p == '=') {
13868       p++;
13869       if (*p == '\"') {
13870         p++;
13871         while (*p && *p != '\"') p++;
13872         if (*p == '\"') p++;
13873       } else {
13874         while (*p && *p != ' ') p++;
13875       }
13876     }
13877   }
13878
13879 }
13880
13881 void
13882 PeriodicUpdatesEvent(newState)
13883      int newState;
13884 {
13885     if (newState == appData.periodicUpdates)
13886       return;
13887
13888     appData.periodicUpdates=newState;
13889
13890     /* Display type changes, so update it now */
13891 //    DisplayAnalysis();
13892
13893     /* Get the ball rolling again... */
13894     if (newState) {
13895         AnalysisPeriodicEvent(1);
13896         StartAnalysisClock();
13897     }
13898 }
13899
13900 void
13901 PonderNextMoveEvent(newState)
13902      int newState;
13903 {
13904     if (newState == appData.ponderNextMove) return;
13905     if (gameMode == EditPosition) EditPositionDone(TRUE);
13906     if (newState) {
13907         SendToProgram("hard\n", &first);
13908         if (gameMode == TwoMachinesPlay) {
13909             SendToProgram("hard\n", &second);
13910         }
13911     } else {
13912         SendToProgram("easy\n", &first);
13913         thinkOutput[0] = NULLCHAR;
13914         if (gameMode == TwoMachinesPlay) {
13915             SendToProgram("easy\n", &second);
13916         }
13917     }
13918     appData.ponderNextMove = newState;
13919 }
13920
13921 void
13922 NewSettingEvent(option, feature, command, value)
13923      char *command;
13924      int option, value, *feature;
13925 {
13926     char buf[MSG_SIZ];
13927
13928     if (gameMode == EditPosition) EditPositionDone(TRUE);
13929     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
13930     if(feature == NULL || *feature) SendToProgram(buf, &first);
13931     if (gameMode == TwoMachinesPlay) {
13932         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
13933     }
13934 }
13935
13936 void
13937 ShowThinkingEvent()
13938 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
13939 {
13940     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
13941     int newState = appData.showThinking
13942         // [HGM] thinking: other features now need thinking output as well
13943         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
13944
13945     if (oldState == newState) return;
13946     oldState = newState;
13947     if (gameMode == EditPosition) EditPositionDone(TRUE);
13948     if (oldState) {
13949         SendToProgram("post\n", &first);
13950         if (gameMode == TwoMachinesPlay) {
13951             SendToProgram("post\n", &second);
13952         }
13953     } else {
13954         SendToProgram("nopost\n", &first);
13955         thinkOutput[0] = NULLCHAR;
13956         if (gameMode == TwoMachinesPlay) {
13957             SendToProgram("nopost\n", &second);
13958         }
13959     }
13960 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
13961 }
13962
13963 void
13964 AskQuestionEvent(title, question, replyPrefix, which)
13965      char *title; char *question; char *replyPrefix; char *which;
13966 {
13967   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
13968   if (pr == NoProc) return;
13969   AskQuestion(title, question, replyPrefix, pr);
13970 }
13971
13972 void
13973 DisplayMove(moveNumber)
13974      int moveNumber;
13975 {
13976     char message[MSG_SIZ];
13977     char res[MSG_SIZ];
13978     char cpThinkOutput[MSG_SIZ];
13979
13980     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
13981
13982     if (moveNumber == forwardMostMove - 1 ||
13983         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13984
13985         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
13986
13987         if (strchr(cpThinkOutput, '\n')) {
13988             *strchr(cpThinkOutput, '\n') = NULLCHAR;
13989         }
13990     } else {
13991         *cpThinkOutput = NULLCHAR;
13992     }
13993
13994     /* [AS] Hide thinking from human user */
13995     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
13996         *cpThinkOutput = NULLCHAR;
13997         if( thinkOutput[0] != NULLCHAR ) {
13998             int i;
13999
14000             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14001                 cpThinkOutput[i] = '.';
14002             }
14003             cpThinkOutput[i] = NULLCHAR;
14004             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14005         }
14006     }
14007
14008     if (moveNumber == forwardMostMove - 1 &&
14009         gameInfo.resultDetails != NULL) {
14010         if (gameInfo.resultDetails[0] == NULLCHAR) {
14011           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14012         } else {
14013           snprintf(res, MSG_SIZ, " {%s} %s",
14014                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14015         }
14016     } else {
14017         res[0] = NULLCHAR;
14018     }
14019
14020     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14021         DisplayMessage(res, cpThinkOutput);
14022     } else {
14023       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14024                 WhiteOnMove(moveNumber) ? " " : ".. ",
14025                 parseList[moveNumber], res);
14026         DisplayMessage(message, cpThinkOutput);
14027     }
14028 }
14029
14030 void
14031 DisplayComment(moveNumber, text)
14032      int moveNumber;
14033      char *text;
14034 {
14035     char title[MSG_SIZ];
14036     char buf[8000]; // comment can be long!
14037     int score, depth;
14038
14039     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14040       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14041     } else {
14042       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14043               WhiteOnMove(moveNumber) ? " " : ".. ",
14044               parseList[moveNumber]);
14045     }
14046     // [HGM] PV info: display PV info together with (or as) comment
14047     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14048       if(text == NULL) text = "";
14049       score = pvInfoList[moveNumber].score;
14050       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14051               depth, (pvInfoList[moveNumber].time+50)/100, text);
14052       text = buf;
14053     }
14054     if (text != NULL && (appData.autoDisplayComment || commentUp))
14055         CommentPopUp(title, text);
14056 }
14057
14058 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14059  * might be busy thinking or pondering.  It can be omitted if your
14060  * gnuchess is configured to stop thinking immediately on any user
14061  * input.  However, that gnuchess feature depends on the FIONREAD
14062  * ioctl, which does not work properly on some flavors of Unix.
14063  */
14064 void
14065 Attention(cps)
14066      ChessProgramState *cps;
14067 {
14068 #if ATTENTION
14069     if (!cps->useSigint) return;
14070     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14071     switch (gameMode) {
14072       case MachinePlaysWhite:
14073       case MachinePlaysBlack:
14074       case TwoMachinesPlay:
14075       case IcsPlayingWhite:
14076       case IcsPlayingBlack:
14077       case AnalyzeMode:
14078       case AnalyzeFile:
14079         /* Skip if we know it isn't thinking */
14080         if (!cps->maybeThinking) return;
14081         if (appData.debugMode)
14082           fprintf(debugFP, "Interrupting %s\n", cps->which);
14083         InterruptChildProcess(cps->pr);
14084         cps->maybeThinking = FALSE;
14085         break;
14086       default:
14087         break;
14088     }
14089 #endif /*ATTENTION*/
14090 }
14091
14092 int
14093 CheckFlags()
14094 {
14095     if (whiteTimeRemaining <= 0) {
14096         if (!whiteFlag) {
14097             whiteFlag = TRUE;
14098             if (appData.icsActive) {
14099                 if (appData.autoCallFlag &&
14100                     gameMode == IcsPlayingBlack && !blackFlag) {
14101                   SendToICS(ics_prefix);
14102                   SendToICS("flag\n");
14103                 }
14104             } else {
14105                 if (blackFlag) {
14106                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14107                 } else {
14108                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14109                     if (appData.autoCallFlag) {
14110                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14111                         return TRUE;
14112                     }
14113                 }
14114             }
14115         }
14116     }
14117     if (blackTimeRemaining <= 0) {
14118         if (!blackFlag) {
14119             blackFlag = TRUE;
14120             if (appData.icsActive) {
14121                 if (appData.autoCallFlag &&
14122                     gameMode == IcsPlayingWhite && !whiteFlag) {
14123                   SendToICS(ics_prefix);
14124                   SendToICS("flag\n");
14125                 }
14126             } else {
14127                 if (whiteFlag) {
14128                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14129                 } else {
14130                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14131                     if (appData.autoCallFlag) {
14132                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14133                         return TRUE;
14134                     }
14135                 }
14136             }
14137         }
14138     }
14139     return FALSE;
14140 }
14141
14142 void
14143 CheckTimeControl()
14144 {
14145     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14146         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14147
14148     /*
14149      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14150      */
14151     if ( !WhiteOnMove(forwardMostMove) ) {
14152         /* White made time control */
14153         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14154         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14155         /* [HGM] time odds: correct new time quota for time odds! */
14156                                             / WhitePlayer()->timeOdds;
14157         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14158     } else {
14159         lastBlack -= blackTimeRemaining;
14160         /* Black made time control */
14161         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14162                                             / WhitePlayer()->other->timeOdds;
14163         lastWhite = whiteTimeRemaining;
14164     }
14165 }
14166
14167 void
14168 DisplayBothClocks()
14169 {
14170     int wom = gameMode == EditPosition ?
14171       !blackPlaysFirst : WhiteOnMove(currentMove);
14172     DisplayWhiteClock(whiteTimeRemaining, wom);
14173     DisplayBlackClock(blackTimeRemaining, !wom);
14174 }
14175
14176
14177 /* Timekeeping seems to be a portability nightmare.  I think everyone
14178    has ftime(), but I'm really not sure, so I'm including some ifdefs
14179    to use other calls if you don't.  Clocks will be less accurate if
14180    you have neither ftime nor gettimeofday.
14181 */
14182
14183 /* VS 2008 requires the #include outside of the function */
14184 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14185 #include <sys/timeb.h>
14186 #endif
14187
14188 /* Get the current time as a TimeMark */
14189 void
14190 GetTimeMark(tm)
14191      TimeMark *tm;
14192 {
14193 #if HAVE_GETTIMEOFDAY
14194
14195     struct timeval timeVal;
14196     struct timezone timeZone;
14197
14198     gettimeofday(&timeVal, &timeZone);
14199     tm->sec = (long) timeVal.tv_sec;
14200     tm->ms = (int) (timeVal.tv_usec / 1000L);
14201
14202 #else /*!HAVE_GETTIMEOFDAY*/
14203 #if HAVE_FTIME
14204
14205 // include <sys/timeb.h> / moved to just above start of function
14206     struct timeb timeB;
14207
14208     ftime(&timeB);
14209     tm->sec = (long) timeB.time;
14210     tm->ms = (int) timeB.millitm;
14211
14212 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14213     tm->sec = (long) time(NULL);
14214     tm->ms = 0;
14215 #endif
14216 #endif
14217 }
14218
14219 /* Return the difference in milliseconds between two
14220    time marks.  We assume the difference will fit in a long!
14221 */
14222 long
14223 SubtractTimeMarks(tm2, tm1)
14224      TimeMark *tm2, *tm1;
14225 {
14226     return 1000L*(tm2->sec - tm1->sec) +
14227            (long) (tm2->ms - tm1->ms);
14228 }
14229
14230
14231 /*
14232  * Code to manage the game clocks.
14233  *
14234  * In tournament play, black starts the clock and then white makes a move.
14235  * We give the human user a slight advantage if he is playing white---the
14236  * clocks don't run until he makes his first move, so it takes zero time.
14237  * Also, we don't account for network lag, so we could get out of sync
14238  * with GNU Chess's clock -- but then, referees are always right.
14239  */
14240
14241 static TimeMark tickStartTM;
14242 static long intendedTickLength;
14243
14244 long
14245 NextTickLength(timeRemaining)
14246      long timeRemaining;
14247 {
14248     long nominalTickLength, nextTickLength;
14249
14250     if (timeRemaining > 0L && timeRemaining <= 10000L)
14251       nominalTickLength = 100L;
14252     else
14253       nominalTickLength = 1000L;
14254     nextTickLength = timeRemaining % nominalTickLength;
14255     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14256
14257     return nextTickLength;
14258 }
14259
14260 /* Adjust clock one minute up or down */
14261 void
14262 AdjustClock(Boolean which, int dir)
14263 {
14264     if(which) blackTimeRemaining += 60000*dir;
14265     else      whiteTimeRemaining += 60000*dir;
14266     DisplayBothClocks();
14267 }
14268
14269 /* Stop clocks and reset to a fresh time control */
14270 void
14271 ResetClocks()
14272 {
14273     (void) StopClockTimer();
14274     if (appData.icsActive) {
14275         whiteTimeRemaining = blackTimeRemaining = 0;
14276     } else if (searchTime) {
14277         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14278         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14279     } else { /* [HGM] correct new time quote for time odds */
14280         whiteTC = blackTC = fullTimeControlString;
14281         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14282         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14283     }
14284     if (whiteFlag || blackFlag) {
14285         DisplayTitle("");
14286         whiteFlag = blackFlag = FALSE;
14287     }
14288     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14289     DisplayBothClocks();
14290 }
14291
14292 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14293
14294 /* Decrement running clock by amount of time that has passed */
14295 void
14296 DecrementClocks()
14297 {
14298     long timeRemaining;
14299     long lastTickLength, fudge;
14300     TimeMark now;
14301
14302     if (!appData.clockMode) return;
14303     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14304
14305     GetTimeMark(&now);
14306
14307     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14308
14309     /* Fudge if we woke up a little too soon */
14310     fudge = intendedTickLength - lastTickLength;
14311     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14312
14313     if (WhiteOnMove(forwardMostMove)) {
14314         if(whiteNPS >= 0) lastTickLength = 0;
14315         timeRemaining = whiteTimeRemaining -= lastTickLength;
14316         if(timeRemaining < 0 && !appData.icsActive) {
14317             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14318             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14319                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14320                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14321             }
14322         }
14323         DisplayWhiteClock(whiteTimeRemaining - fudge,
14324                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14325     } else {
14326         if(blackNPS >= 0) lastTickLength = 0;
14327         timeRemaining = blackTimeRemaining -= lastTickLength;
14328         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14329             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14330             if(suddenDeath) {
14331                 blackStartMove = forwardMostMove;
14332                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14333             }
14334         }
14335         DisplayBlackClock(blackTimeRemaining - fudge,
14336                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14337     }
14338     if (CheckFlags()) return;
14339
14340     tickStartTM = now;
14341     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14342     StartClockTimer(intendedTickLength);
14343
14344     /* if the time remaining has fallen below the alarm threshold, sound the
14345      * alarm. if the alarm has sounded and (due to a takeback or time control
14346      * with increment) the time remaining has increased to a level above the
14347      * threshold, reset the alarm so it can sound again.
14348      */
14349
14350     if (appData.icsActive && appData.icsAlarm) {
14351
14352         /* make sure we are dealing with the user's clock */
14353         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14354                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14355            )) return;
14356
14357         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14358             alarmSounded = FALSE;
14359         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14360             PlayAlarmSound();
14361             alarmSounded = TRUE;
14362         }
14363     }
14364 }
14365
14366
14367 /* A player has just moved, so stop the previously running
14368    clock and (if in clock mode) start the other one.
14369    We redisplay both clocks in case we're in ICS mode, because
14370    ICS gives us an update to both clocks after every move.
14371    Note that this routine is called *after* forwardMostMove
14372    is updated, so the last fractional tick must be subtracted
14373    from the color that is *not* on move now.
14374 */
14375 void
14376 SwitchClocks(int newMoveNr)
14377 {
14378     long lastTickLength;
14379     TimeMark now;
14380     int flagged = FALSE;
14381
14382     GetTimeMark(&now);
14383
14384     if (StopClockTimer() && appData.clockMode) {
14385         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14386         if (!WhiteOnMove(forwardMostMove)) {
14387             if(blackNPS >= 0) lastTickLength = 0;
14388             blackTimeRemaining -= lastTickLength;
14389            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14390 //         if(pvInfoList[forwardMostMove-1].time == -1)
14391                  pvInfoList[forwardMostMove-1].time =               // use GUI time
14392                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14393         } else {
14394            if(whiteNPS >= 0) lastTickLength = 0;
14395            whiteTimeRemaining -= lastTickLength;
14396            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14397 //         if(pvInfoList[forwardMostMove-1].time == -1)
14398                  pvInfoList[forwardMostMove-1].time =
14399                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14400         }
14401         flagged = CheckFlags();
14402     }
14403     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14404     CheckTimeControl();
14405
14406     if (flagged || !appData.clockMode) return;
14407
14408     switch (gameMode) {
14409       case MachinePlaysBlack:
14410       case MachinePlaysWhite:
14411       case BeginningOfGame:
14412         if (pausing) return;
14413         break;
14414
14415       case EditGame:
14416       case PlayFromGameFile:
14417       case IcsExamining:
14418         return;
14419
14420       default:
14421         break;
14422     }
14423
14424     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14425         if(WhiteOnMove(forwardMostMove))
14426              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14427         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14428     }
14429
14430     tickStartTM = now;
14431     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14432       whiteTimeRemaining : blackTimeRemaining);
14433     StartClockTimer(intendedTickLength);
14434 }
14435
14436
14437 /* Stop both clocks */
14438 void
14439 StopClocks()
14440 {
14441     long lastTickLength;
14442     TimeMark now;
14443
14444     if (!StopClockTimer()) return;
14445     if (!appData.clockMode) return;
14446
14447     GetTimeMark(&now);
14448
14449     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14450     if (WhiteOnMove(forwardMostMove)) {
14451         if(whiteNPS >= 0) lastTickLength = 0;
14452         whiteTimeRemaining -= lastTickLength;
14453         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14454     } else {
14455         if(blackNPS >= 0) lastTickLength = 0;
14456         blackTimeRemaining -= lastTickLength;
14457         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14458     }
14459     CheckFlags();
14460 }
14461
14462 /* Start clock of player on move.  Time may have been reset, so
14463    if clock is already running, stop and restart it. */
14464 void
14465 StartClocks()
14466 {
14467     (void) StopClockTimer(); /* in case it was running already */
14468     DisplayBothClocks();
14469     if (CheckFlags()) return;
14470
14471     if (!appData.clockMode) return;
14472     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14473
14474     GetTimeMark(&tickStartTM);
14475     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14476       whiteTimeRemaining : blackTimeRemaining);
14477
14478    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14479     whiteNPS = blackNPS = -1;
14480     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14481        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14482         whiteNPS = first.nps;
14483     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14484        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14485         blackNPS = first.nps;
14486     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14487         whiteNPS = second.nps;
14488     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14489         blackNPS = second.nps;
14490     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14491
14492     StartClockTimer(intendedTickLength);
14493 }
14494
14495 char *
14496 TimeString(ms)
14497      long ms;
14498 {
14499     long second, minute, hour, day;
14500     char *sign = "";
14501     static char buf[32];
14502
14503     if (ms > 0 && ms <= 9900) {
14504       /* convert milliseconds to tenths, rounding up */
14505       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14506
14507       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14508       return buf;
14509     }
14510
14511     /* convert milliseconds to seconds, rounding up */
14512     /* use floating point to avoid strangeness of integer division
14513        with negative dividends on many machines */
14514     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14515
14516     if (second < 0) {
14517         sign = "-";
14518         second = -second;
14519     }
14520
14521     day = second / (60 * 60 * 24);
14522     second = second % (60 * 60 * 24);
14523     hour = second / (60 * 60);
14524     second = second % (60 * 60);
14525     minute = second / 60;
14526     second = second % 60;
14527
14528     if (day > 0)
14529       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14530               sign, day, hour, minute, second);
14531     else if (hour > 0)
14532       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14533     else
14534       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
14535
14536     return buf;
14537 }
14538
14539
14540 /*
14541  * This is necessary because some C libraries aren't ANSI C compliant yet.
14542  */
14543 char *
14544 StrStr(string, match)
14545      char *string, *match;
14546 {
14547     int i, length;
14548
14549     length = strlen(match);
14550
14551     for (i = strlen(string) - length; i >= 0; i--, string++)
14552       if (!strncmp(match, string, length))
14553         return string;
14554
14555     return NULL;
14556 }
14557
14558 char *
14559 StrCaseStr(string, match)
14560      char *string, *match;
14561 {
14562     int i, j, length;
14563
14564     length = strlen(match);
14565
14566     for (i = strlen(string) - length; i >= 0; i--, string++) {
14567         for (j = 0; j < length; j++) {
14568             if (ToLower(match[j]) != ToLower(string[j]))
14569               break;
14570         }
14571         if (j == length) return string;
14572     }
14573
14574     return NULL;
14575 }
14576
14577 #ifndef _amigados
14578 int
14579 StrCaseCmp(s1, s2)
14580      char *s1, *s2;
14581 {
14582     char c1, c2;
14583
14584     for (;;) {
14585         c1 = ToLower(*s1++);
14586         c2 = ToLower(*s2++);
14587         if (c1 > c2) return 1;
14588         if (c1 < c2) return -1;
14589         if (c1 == NULLCHAR) return 0;
14590     }
14591 }
14592
14593
14594 int
14595 ToLower(c)
14596      int c;
14597 {
14598     return isupper(c) ? tolower(c) : c;
14599 }
14600
14601
14602 int
14603 ToUpper(c)
14604      int c;
14605 {
14606     return islower(c) ? toupper(c) : c;
14607 }
14608 #endif /* !_amigados    */
14609
14610 char *
14611 StrSave(s)
14612      char *s;
14613 {
14614   char *ret;
14615
14616   if ((ret = (char *) malloc(strlen(s) + 1)))
14617     {
14618       safeStrCpy(ret, s, strlen(s)+1);
14619     }
14620   return ret;
14621 }
14622
14623 char *
14624 StrSavePtr(s, savePtr)
14625      char *s, **savePtr;
14626 {
14627     if (*savePtr) {
14628         free(*savePtr);
14629     }
14630     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
14631       safeStrCpy(*savePtr, s, strlen(s)+1);
14632     }
14633     return(*savePtr);
14634 }
14635
14636 char *
14637 PGNDate()
14638 {
14639     time_t clock;
14640     struct tm *tm;
14641     char buf[MSG_SIZ];
14642
14643     clock = time((time_t *)NULL);
14644     tm = localtime(&clock);
14645     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
14646             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
14647     return StrSave(buf);
14648 }
14649
14650
14651 char *
14652 PositionToFEN(move, overrideCastling)
14653      int move;
14654      char *overrideCastling;
14655 {
14656     int i, j, fromX, fromY, toX, toY;
14657     int whiteToPlay;
14658     char buf[128];
14659     char *p, *q;
14660     int emptycount;
14661     ChessSquare piece;
14662
14663     whiteToPlay = (gameMode == EditPosition) ?
14664       !blackPlaysFirst : (move % 2 == 0);
14665     p = buf;
14666
14667     /* Piece placement data */
14668     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14669         emptycount = 0;
14670         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14671             if (boards[move][i][j] == EmptySquare) {
14672                 emptycount++;
14673             } else { ChessSquare piece = boards[move][i][j];
14674                 if (emptycount > 0) {
14675                     if(emptycount<10) /* [HGM] can be >= 10 */
14676                         *p++ = '0' + emptycount;
14677                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14678                     emptycount = 0;
14679                 }
14680                 if(PieceToChar(piece) == '+') {
14681                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
14682                     *p++ = '+';
14683                     piece = (ChessSquare)(DEMOTED piece);
14684                 }
14685                 *p++ = PieceToChar(piece);
14686                 if(p[-1] == '~') {
14687                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
14688                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
14689                     *p++ = '~';
14690                 }
14691             }
14692         }
14693         if (emptycount > 0) {
14694             if(emptycount<10) /* [HGM] can be >= 10 */
14695                 *p++ = '0' + emptycount;
14696             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
14697             emptycount = 0;
14698         }
14699         *p++ = '/';
14700     }
14701     *(p - 1) = ' ';
14702
14703     /* [HGM] print Crazyhouse or Shogi holdings */
14704     if( gameInfo.holdingsWidth ) {
14705         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
14706         q = p;
14707         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
14708             piece = boards[move][i][BOARD_WIDTH-1];
14709             if( piece != EmptySquare )
14710               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
14711                   *p++ = PieceToChar(piece);
14712         }
14713         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
14714             piece = boards[move][BOARD_HEIGHT-i-1][0];
14715             if( piece != EmptySquare )
14716               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
14717                   *p++ = PieceToChar(piece);
14718         }
14719
14720         if( q == p ) *p++ = '-';
14721         *p++ = ']';
14722         *p++ = ' ';
14723     }
14724
14725     /* Active color */
14726     *p++ = whiteToPlay ? 'w' : 'b';
14727     *p++ = ' ';
14728
14729   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
14730     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
14731   } else {
14732   if(nrCastlingRights) {
14733      q = p;
14734      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
14735        /* [HGM] write directly from rights */
14736            if(boards[move][CASTLING][2] != NoRights &&
14737               boards[move][CASTLING][0] != NoRights   )
14738                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
14739            if(boards[move][CASTLING][2] != NoRights &&
14740               boards[move][CASTLING][1] != NoRights   )
14741                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
14742            if(boards[move][CASTLING][5] != NoRights &&
14743               boards[move][CASTLING][3] != NoRights   )
14744                 *p++ = boards[move][CASTLING][3] + AAA;
14745            if(boards[move][CASTLING][5] != NoRights &&
14746               boards[move][CASTLING][4] != NoRights   )
14747                 *p++ = boards[move][CASTLING][4] + AAA;
14748      } else {
14749
14750         /* [HGM] write true castling rights */
14751         if( nrCastlingRights == 6 ) {
14752             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
14753                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
14754             if(boards[move][CASTLING][1] == BOARD_LEFT &&
14755                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
14756             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
14757                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
14758             if(boards[move][CASTLING][4] == BOARD_LEFT &&
14759                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
14760         }
14761      }
14762      if (q == p) *p++ = '-'; /* No castling rights */
14763      *p++ = ' ';
14764   }
14765
14766   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
14767      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
14768     /* En passant target square */
14769     if (move > backwardMostMove) {
14770         fromX = moveList[move - 1][0] - AAA;
14771         fromY = moveList[move - 1][1] - ONE;
14772         toX = moveList[move - 1][2] - AAA;
14773         toY = moveList[move - 1][3] - ONE;
14774         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
14775             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
14776             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
14777             fromX == toX) {
14778             /* 2-square pawn move just happened */
14779             *p++ = toX + AAA;
14780             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14781         } else {
14782             *p++ = '-';
14783         }
14784     } else if(move == backwardMostMove) {
14785         // [HGM] perhaps we should always do it like this, and forget the above?
14786         if((signed char)boards[move][EP_STATUS] >= 0) {
14787             *p++ = boards[move][EP_STATUS] + AAA;
14788             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
14789         } else {
14790             *p++ = '-';
14791         }
14792     } else {
14793         *p++ = '-';
14794     }
14795     *p++ = ' ';
14796   }
14797   }
14798
14799     /* [HGM] find reversible plies */
14800     {   int i = 0, j=move;
14801
14802         if (appData.debugMode) { int k;
14803             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
14804             for(k=backwardMostMove; k<=forwardMostMove; k++)
14805                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
14806
14807         }
14808
14809         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
14810         if( j == backwardMostMove ) i += initialRulePlies;
14811         sprintf(p, "%d ", i);
14812         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
14813     }
14814     /* Fullmove number */
14815     sprintf(p, "%d", (move / 2) + 1);
14816
14817     return StrSave(buf);
14818 }
14819
14820 Boolean
14821 ParseFEN(board, blackPlaysFirst, fen)
14822     Board board;
14823      int *blackPlaysFirst;
14824      char *fen;
14825 {
14826     int i, j;
14827     char *p, c;
14828     int emptycount;
14829     ChessSquare piece;
14830
14831     p = fen;
14832
14833     /* [HGM] by default clear Crazyhouse holdings, if present */
14834     if(gameInfo.holdingsWidth) {
14835        for(i=0; i<BOARD_HEIGHT; i++) {
14836            board[i][0]             = EmptySquare; /* black holdings */
14837            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
14838            board[i][1]             = (ChessSquare) 0; /* black counts */
14839            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
14840        }
14841     }
14842
14843     /* Piece placement data */
14844     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14845         j = 0;
14846         for (;;) {
14847             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
14848                 if (*p == '/') p++;
14849                 emptycount = gameInfo.boardWidth - j;
14850                 while (emptycount--)
14851                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14852                 break;
14853 #if(BOARD_FILES >= 10)
14854             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
14855                 p++; emptycount=10;
14856                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14857                 while (emptycount--)
14858                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14859 #endif
14860             } else if (isdigit(*p)) {
14861                 emptycount = *p++ - '0';
14862                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
14863                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
14864                 while (emptycount--)
14865                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
14866             } else if (*p == '+' || isalpha(*p)) {
14867                 if (j >= gameInfo.boardWidth) return FALSE;
14868                 if(*p=='+') {
14869                     piece = CharToPiece(*++p);
14870                     if(piece == EmptySquare) return FALSE; /* unknown piece */
14871                     piece = (ChessSquare) (PROMOTED piece ); p++;
14872                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
14873                 } else piece = CharToPiece(*p++);
14874
14875                 if(piece==EmptySquare) return FALSE; /* unknown piece */
14876                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
14877                     piece = (ChessSquare) (PROMOTED piece);
14878                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
14879                     p++;
14880                 }
14881                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
14882             } else {
14883                 return FALSE;
14884             }
14885         }
14886     }
14887     while (*p == '/' || *p == ' ') p++;
14888
14889     /* [HGM] look for Crazyhouse holdings here */
14890     while(*p==' ') p++;
14891     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
14892         if(*p == '[') p++;
14893         if(*p == '-' ) p++; /* empty holdings */ else {
14894             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
14895             /* if we would allow FEN reading to set board size, we would   */
14896             /* have to add holdings and shift the board read so far here   */
14897             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
14898                 p++;
14899                 if((int) piece >= (int) BlackPawn ) {
14900                     i = (int)piece - (int)BlackPawn;
14901                     i = PieceToNumber((ChessSquare)i);
14902                     if( i >= gameInfo.holdingsSize ) return FALSE;
14903                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
14904                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
14905                 } else {
14906                     i = (int)piece - (int)WhitePawn;
14907                     i = PieceToNumber((ChessSquare)i);
14908                     if( i >= gameInfo.holdingsSize ) return FALSE;
14909                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
14910                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
14911                 }
14912             }
14913         }
14914         if(*p == ']') p++;
14915     }
14916
14917     while(*p == ' ') p++;
14918
14919     /* Active color */
14920     c = *p++;
14921     if(appData.colorNickNames) {
14922       if( c == appData.colorNickNames[0] ) c = 'w'; else
14923       if( c == appData.colorNickNames[1] ) c = 'b';
14924     }
14925     switch (c) {
14926       case 'w':
14927         *blackPlaysFirst = FALSE;
14928         break;
14929       case 'b':
14930         *blackPlaysFirst = TRUE;
14931         break;
14932       default:
14933         return FALSE;
14934     }
14935
14936     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
14937     /* return the extra info in global variiables             */
14938
14939     /* set defaults in case FEN is incomplete */
14940     board[EP_STATUS] = EP_UNKNOWN;
14941     for(i=0; i<nrCastlingRights; i++ ) {
14942         board[CASTLING][i] =
14943             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
14944     }   /* assume possible unless obviously impossible */
14945     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
14946     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
14947     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
14948                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
14949     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
14950     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
14951     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
14952                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
14953     FENrulePlies = 0;
14954
14955     while(*p==' ') p++;
14956     if(nrCastlingRights) {
14957       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
14958           /* castling indicator present, so default becomes no castlings */
14959           for(i=0; i<nrCastlingRights; i++ ) {
14960                  board[CASTLING][i] = NoRights;
14961           }
14962       }
14963       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
14964              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
14965              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
14966              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
14967         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
14968
14969         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
14970             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
14971             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
14972         }
14973         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
14974             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
14975         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
14976                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
14977         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
14978                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
14979         switch(c) {
14980           case'K':
14981               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
14982               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
14983               board[CASTLING][2] = whiteKingFile;
14984               break;
14985           case'Q':
14986               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
14987               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
14988               board[CASTLING][2] = whiteKingFile;
14989               break;
14990           case'k':
14991               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
14992               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
14993               board[CASTLING][5] = blackKingFile;
14994               break;
14995           case'q':
14996               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
14997               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
14998               board[CASTLING][5] = blackKingFile;
14999           case '-':
15000               break;
15001           default: /* FRC castlings */
15002               if(c >= 'a') { /* black rights */
15003                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15004                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15005                   if(i == BOARD_RGHT) break;
15006                   board[CASTLING][5] = i;
15007                   c -= AAA;
15008                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15009                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15010                   if(c > i)
15011                       board[CASTLING][3] = c;
15012                   else
15013                       board[CASTLING][4] = c;
15014               } else { /* white rights */
15015                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15016                     if(board[0][i] == WhiteKing) break;
15017                   if(i == BOARD_RGHT) break;
15018                   board[CASTLING][2] = i;
15019                   c -= AAA - 'a' + 'A';
15020                   if(board[0][c] >= WhiteKing) break;
15021                   if(c > i)
15022                       board[CASTLING][0] = c;
15023                   else
15024                       board[CASTLING][1] = c;
15025               }
15026         }
15027       }
15028       for(i=0; i<nrCastlingRights; i++)
15029         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15030     if (appData.debugMode) {
15031         fprintf(debugFP, "FEN castling rights:");
15032         for(i=0; i<nrCastlingRights; i++)
15033         fprintf(debugFP, " %d", board[CASTLING][i]);
15034         fprintf(debugFP, "\n");
15035     }
15036
15037       while(*p==' ') p++;
15038     }
15039
15040     /* read e.p. field in games that know e.p. capture */
15041     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15042        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15043       if(*p=='-') {
15044         p++; board[EP_STATUS] = EP_NONE;
15045       } else {
15046          char c = *p++ - AAA;
15047
15048          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15049          if(*p >= '0' && *p <='9') p++;
15050          board[EP_STATUS] = c;
15051       }
15052     }
15053
15054
15055     if(sscanf(p, "%d", &i) == 1) {
15056         FENrulePlies = i; /* 50-move ply counter */
15057         /* (The move number is still ignored)    */
15058     }
15059
15060     return TRUE;
15061 }
15062
15063 void
15064 EditPositionPasteFEN(char *fen)
15065 {
15066   if (fen != NULL) {
15067     Board initial_position;
15068
15069     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15070       DisplayError(_("Bad FEN position in clipboard"), 0);
15071       return ;
15072     } else {
15073       int savedBlackPlaysFirst = blackPlaysFirst;
15074       EditPositionEvent();
15075       blackPlaysFirst = savedBlackPlaysFirst;
15076       CopyBoard(boards[0], initial_position);
15077       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15078       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15079       DisplayBothClocks();
15080       DrawPosition(FALSE, boards[currentMove]);
15081     }
15082   }
15083 }
15084
15085 static char cseq[12] = "\\   ";
15086
15087 Boolean set_cont_sequence(char *new_seq)
15088 {
15089     int len;
15090     Boolean ret;
15091
15092     // handle bad attempts to set the sequence
15093         if (!new_seq)
15094                 return 0; // acceptable error - no debug
15095
15096     len = strlen(new_seq);
15097     ret = (len > 0) && (len < sizeof(cseq));
15098     if (ret)
15099       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15100     else if (appData.debugMode)
15101       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15102     return ret;
15103 }
15104
15105 /*
15106     reformat a source message so words don't cross the width boundary.  internal
15107     newlines are not removed.  returns the wrapped size (no null character unless
15108     included in source message).  If dest is NULL, only calculate the size required
15109     for the dest buffer.  lp argument indicats line position upon entry, and it's
15110     passed back upon exit.
15111 */
15112 int wrap(char *dest, char *src, int count, int width, int *lp)
15113 {
15114     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15115
15116     cseq_len = strlen(cseq);
15117     old_line = line = *lp;
15118     ansi = len = clen = 0;
15119
15120     for (i=0; i < count; i++)
15121     {
15122         if (src[i] == '\033')
15123             ansi = 1;
15124
15125         // if we hit the width, back up
15126         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15127         {
15128             // store i & len in case the word is too long
15129             old_i = i, old_len = len;
15130
15131             // find the end of the last word
15132             while (i && src[i] != ' ' && src[i] != '\n')
15133             {
15134                 i--;
15135                 len--;
15136             }
15137
15138             // word too long?  restore i & len before splitting it
15139             if ((old_i-i+clen) >= width)
15140             {
15141                 i = old_i;
15142                 len = old_len;
15143             }
15144
15145             // extra space?
15146             if (i && src[i-1] == ' ')
15147                 len--;
15148
15149             if (src[i] != ' ' && src[i] != '\n')
15150             {
15151                 i--;
15152                 if (len)
15153                     len--;
15154             }
15155
15156             // now append the newline and continuation sequence
15157             if (dest)
15158                 dest[len] = '\n';
15159             len++;
15160             if (dest)
15161                 strncpy(dest+len, cseq, cseq_len);
15162             len += cseq_len;
15163             line = cseq_len;
15164             clen = cseq_len;
15165             continue;
15166         }
15167
15168         if (dest)
15169             dest[len] = src[i];
15170         len++;
15171         if (!ansi)
15172             line++;
15173         if (src[i] == '\n')
15174             line = 0;
15175         if (src[i] == 'm')
15176             ansi = 0;
15177     }
15178     if (dest && appData.debugMode)
15179     {
15180         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15181             count, width, line, len, *lp);
15182         show_bytes(debugFP, src, count);
15183         fprintf(debugFP, "\ndest: ");
15184         show_bytes(debugFP, dest, len);
15185         fprintf(debugFP, "\n");
15186     }
15187     *lp = dest ? line : old_line;
15188
15189     return len;
15190 }
15191
15192 // [HGM] vari: routines for shelving variations
15193
15194 void
15195 PushTail(int firstMove, int lastMove)
15196 {
15197         int i, j, nrMoves = lastMove - firstMove;
15198
15199         if(appData.icsActive) { // only in local mode
15200                 forwardMostMove = currentMove; // mimic old ICS behavior
15201                 return;
15202         }
15203         if(storedGames >= MAX_VARIATIONS-1) return;
15204
15205         // push current tail of game on stack
15206         savedResult[storedGames] = gameInfo.result;
15207         savedDetails[storedGames] = gameInfo.resultDetails;
15208         gameInfo.resultDetails = NULL;
15209         savedFirst[storedGames] = firstMove;
15210         savedLast [storedGames] = lastMove;
15211         savedFramePtr[storedGames] = framePtr;
15212         framePtr -= nrMoves; // reserve space for the boards
15213         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15214             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15215             for(j=0; j<MOVE_LEN; j++)
15216                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15217             for(j=0; j<2*MOVE_LEN; j++)
15218                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15219             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15220             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15221             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15222             pvInfoList[firstMove+i-1].depth = 0;
15223             commentList[framePtr+i] = commentList[firstMove+i];
15224             commentList[firstMove+i] = NULL;
15225         }
15226
15227         storedGames++;
15228         forwardMostMove = firstMove; // truncate game so we can start variation
15229         if(storedGames == 1) GreyRevert(FALSE);
15230 }
15231
15232 Boolean
15233 PopTail(Boolean annotate)
15234 {
15235         int i, j, nrMoves;
15236         char buf[8000], moveBuf[20];
15237
15238         if(appData.icsActive) return FALSE; // only in local mode
15239         if(!storedGames) return FALSE; // sanity
15240         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15241
15242         storedGames--;
15243         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15244         nrMoves = savedLast[storedGames] - currentMove;
15245         if(annotate) {
15246                 int cnt = 10;
15247                 if(!WhiteOnMove(currentMove))
15248                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15249                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15250                 for(i=currentMove; i<forwardMostMove; i++) {
15251                         if(WhiteOnMove(i))
15252                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15253                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15254                         strcat(buf, moveBuf);
15255                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15256                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15257                 }
15258                 strcat(buf, ")");
15259         }
15260         for(i=1; i<=nrMoves; i++) { // copy last variation back
15261             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15262             for(j=0; j<MOVE_LEN; j++)
15263                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15264             for(j=0; j<2*MOVE_LEN; j++)
15265                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15266             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15267             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15268             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15269             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15270             commentList[currentMove+i] = commentList[framePtr+i];
15271             commentList[framePtr+i] = NULL;
15272         }
15273         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15274         framePtr = savedFramePtr[storedGames];
15275         gameInfo.result = savedResult[storedGames];
15276         if(gameInfo.resultDetails != NULL) {
15277             free(gameInfo.resultDetails);
15278       }
15279         gameInfo.resultDetails = savedDetails[storedGames];
15280         forwardMostMove = currentMove + nrMoves;
15281         if(storedGames == 0) GreyRevert(TRUE);
15282         return TRUE;
15283 }
15284
15285 void
15286 CleanupTail()
15287 {       // remove all shelved variations
15288         int i;
15289         for(i=0; i<storedGames; i++) {
15290             if(savedDetails[i])
15291                 free(savedDetails[i]);
15292             savedDetails[i] = NULL;
15293         }
15294         for(i=framePtr; i<MAX_MOVES; i++) {
15295                 if(commentList[i]) free(commentList[i]);
15296                 commentList[i] = NULL;
15297         }
15298         framePtr = MAX_MOVES-1;
15299         storedGames = 0;
15300 }
15301
15302 void
15303 LoadVariation(int index, char *text)
15304 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15305         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15306         int level = 0, move;
15307
15308         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15309         // first find outermost bracketing variation
15310         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15311             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15312                 if(*p == '{') wait = '}'; else
15313                 if(*p == '[') wait = ']'; else
15314                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15315                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15316             }
15317             if(*p == wait) wait = NULLCHAR; // closing ]} found
15318             p++;
15319         }
15320         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15321         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15322         end[1] = NULLCHAR; // clip off comment beyond variation
15323         ToNrEvent(currentMove-1);
15324         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15325         // kludge: use ParsePV() to append variation to game
15326         move = currentMove;
15327         ParsePV(start, TRUE);
15328         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15329         ClearPremoveHighlights();
15330         CommentPopDown();
15331         ToNrEvent(currentMove+1);
15332 }
15333