138422a1a8b358846510a609071eeaddb9975690
[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, 2011 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 int flock(int f, int code);
61 #define LOCK_EX 2
62
63 #else
64
65 #define DoSleep( n ) if( (n) >= 0) sleep(n)
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "gettext.h"
133
134 #ifdef ENABLE_NLS
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
138 #else
139 # ifdef WIN32
140 #   define _(s) T_(s)
141 #   define N_(s) s
142 # else
143 #   define _(s) (s)
144 #   define N_(s) s
145 #   define T_(s) s
146 # endif
147 #endif
148
149
150 /* A point in time */
151 typedef struct {
152     long sec;  /* Assuming this is >= 32 bits */
153     int ms;    /* Assuming this is >= 16 bits */
154 } TimeMark;
155
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158                          char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160                       char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
172                                                                                 Board board));
173 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
174 void ShowMove P((int fromX, int fromY, int toX, int toY));
175 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
176                    /*char*/int promoChar));
177 void BackwardInner P((int target));
178 void ForwardInner P((int target));
179 int Adjudicate P((ChessProgramState *cps));
180 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
181 void EditPositionDone P((Boolean fakeRights));
182 void PrintOpponents P((FILE *fp));
183 void PrintPosition P((FILE *fp, int move));
184 void StartChessProgram P((ChessProgramState *cps));
185 void SendToProgram P((char *message, ChessProgramState *cps));
186 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
187 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
188                            char *buf, int count, int error));
189 void SendTimeControl P((ChessProgramState *cps,
190                         int mps, long tc, int inc, int sd, int st));
191 char *TimeControlTagValue P((void));
192 void Attention P((ChessProgramState *cps));
193 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
194 void ResurrectChessProgram P((void));
195 void DisplayComment P((int moveNumber, char *text));
196 void DisplayMove P((int moveNumber));
197
198 void ParseGameHistory P((char *game));
199 void ParseBoard12 P((char *string));
200 void KeepAlive P((void));
201 void StartClocks P((void));
202 void SwitchClocks P((int nr));
203 void StopClocks P((void));
204 void ResetClocks P((void));
205 char *PGNDate P((void));
206 void SetGameInfo P((void));
207 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
208 int RegisterMove P((void));
209 void MakeRegisteredMove P((void));
210 void TruncateGame P((void));
211 int looking_at P((char *, int *, char *));
212 void CopyPlayerNameIntoFileName P((char **, char *));
213 char *SavePart P((char *));
214 int SaveGameOldStyle P((FILE *));
215 int SaveGamePGN P((FILE *));
216 void GetTimeMark P((TimeMark *));
217 long SubtractTimeMarks P((TimeMark *, TimeMark *));
218 int CheckFlags P((void));
219 long NextTickLength P((long));
220 void CheckTimeControl P((void));
221 void show_bytes P((FILE *, char *, int));
222 int string_to_rating P((char *str));
223 void ParseFeatures P((char* args, ChessProgramState *cps));
224 void InitBackEnd3 P((void));
225 void FeatureDone P((ChessProgramState* cps, int val));
226 void InitChessProgram P((ChessProgramState *cps, int setup));
227 void OutputKibitz(int window, char *text);
228 int PerpetualChase(int first, int last);
229 int EngineOutputIsUp();
230 void InitDrawingSizes(int x, int y);
231
232 #ifdef WIN32
233        extern void ConsoleCreate();
234 #endif
235
236 ChessProgramState *WhitePlayer();
237 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
238 int VerifyDisplayMode P(());
239
240 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
241 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
242 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
243 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
244 void ics_update_width P((int new_width));
245 extern char installDir[MSG_SIZ];
246 VariantClass startVariant; /* [HGM] nicks: initial variant */
247
248 extern int tinyLayout, smallLayout;
249 ChessProgramStats programStats;
250 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
251 int endPV = -1;
252 static int exiting = 0; /* [HGM] moved to top */
253 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
254 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
255 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
256 int partnerHighlight[2];
257 Boolean partnerBoardValid = 0;
258 char partnerStatus[MSG_SIZ];
259 Boolean partnerUp;
260 Boolean originalFlip;
261 Boolean twoBoards = 0;
262 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
263 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
264 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
265 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
266 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
267 int opponentKibitzes;
268 int lastSavedGame; /* [HGM] save: ID of game */
269 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
270 extern int chatCount;
271 int chattingPartner;
272 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277
278 /* States for ics_getting_history */
279 #define H_FALSE 0
280 #define H_REQUESTED 1
281 #define H_GOT_REQ_HEADER 2
282 #define H_GOT_UNREQ_HEADER 3
283 #define H_GETTING_MOVES 4
284 #define H_GOT_UNWANTED_HEADER 5
285
286 /* whosays values for GameEnds */
287 #define GE_ICS 0
288 #define GE_ENGINE 1
289 #define GE_PLAYER 2
290 #define GE_FILE 3
291 #define GE_XBOARD 4
292 #define GE_ENGINE1 5
293 #define GE_ENGINE2 6
294
295 /* Maximum number of games in a cmail message */
296 #define CMAIL_MAX_GAMES 20
297
298 /* Different types of move when calling RegisterMove */
299 #define CMAIL_MOVE   0
300 #define CMAIL_RESIGN 1
301 #define CMAIL_DRAW   2
302 #define CMAIL_ACCEPT 3
303
304 /* Different types of result to remember for each game */
305 #define CMAIL_NOT_RESULT 0
306 #define CMAIL_OLD_RESULT 1
307 #define CMAIL_NEW_RESULT 2
308
309 /* Telnet protocol constants */
310 #define TN_WILL 0373
311 #define TN_WONT 0374
312 #define TN_DO   0375
313 #define TN_DONT 0376
314 #define TN_IAC  0377
315 #define TN_ECHO 0001
316 #define TN_SGA  0003
317 #define TN_PORT 23
318
319 char*
320 safeStrCpy( char *dst, const char *src, size_t count )
321 { // [HGM] made safe
322   int i;
323   assert( dst != NULL );
324   assert( src != NULL );
325   assert( count > 0 );
326
327   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
328   if(  i == count && dst[count-1] != NULLCHAR)
329     {
330       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
331       if(appData.debugMode)
332       fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
333     }
334
335   return dst;
336 }
337
338 /* Some compiler can't cast u64 to double
339  * This function do the job for us:
340
341  * We use the highest bit for cast, this only
342  * works if the highest bit is not
343  * in use (This should not happen)
344  *
345  * We used this for all compiler
346  */
347 double
348 u64ToDouble(u64 value)
349 {
350   double r;
351   u64 tmp = value & u64Const(0x7fffffffffffffff);
352   r = (double)(s64)tmp;
353   if (value & u64Const(0x8000000000000000))
354        r +=  9.2233720368547758080e18; /* 2^63 */
355  return r;
356 }
357
358 /* Fake up flags for now, as we aren't keeping track of castling
359    availability yet. [HGM] Change of logic: the flag now only
360    indicates the type of castlings allowed by the rule of the game.
361    The actual rights themselves are maintained in the array
362    castlingRights, as part of the game history, and are not probed
363    by this function.
364  */
365 int
366 PosFlags(index)
367 {
368   int flags = F_ALL_CASTLE_OK;
369   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
370   switch (gameInfo.variant) {
371   case VariantSuicide:
372     flags &= ~F_ALL_CASTLE_OK;
373   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
374     flags |= F_IGNORE_CHECK;
375   case VariantLosers:
376     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
377     break;
378   case VariantAtomic:
379     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
380     break;
381   case VariantKriegspiel:
382     flags |= F_KRIEGSPIEL_CAPTURE;
383     break;
384   case VariantCapaRandom:
385   case VariantFischeRandom:
386     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
387   case VariantNoCastle:
388   case VariantShatranj:
389   case VariantCourier:
390   case VariantMakruk:
391     flags &= ~F_ALL_CASTLE_OK;
392     break;
393   default:
394     break;
395   }
396   return flags;
397 }
398
399 FILE *gameFileFP, *debugFP;
400
401 /*
402     [AS] Note: sometimes, the sscanf() function is used to parse the input
403     into a fixed-size buffer. Because of this, we must be prepared to
404     receive strings as long as the size of the input buffer, which is currently
405     set to 4K for Windows and 8K for the rest.
406     So, we must either allocate sufficiently large buffers here, or
407     reduce the size of the input buffer in the input reading part.
408 */
409
410 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
411 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
412 char thinkOutput1[MSG_SIZ*10];
413
414 ChessProgramState first, second;
415
416 /* premove variables */
417 int premoveToX = 0;
418 int premoveToY = 0;
419 int premoveFromX = 0;
420 int premoveFromY = 0;
421 int premovePromoChar = 0;
422 int gotPremove = 0;
423 Boolean alarmSounded;
424 /* end premove variables */
425
426 char *ics_prefix = "$";
427 int ics_type = ICS_GENERIC;
428
429 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
430 int pauseExamForwardMostMove = 0;
431 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
432 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
433 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
434 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
435 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
436 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
437 int whiteFlag = FALSE, blackFlag = FALSE;
438 int userOfferedDraw = FALSE;
439 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
440 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
441 int cmailMoveType[CMAIL_MAX_GAMES];
442 long ics_clock_paused = 0;
443 ProcRef icsPR = NoProc, cmailPR = NoProc;
444 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
445 GameMode gameMode = BeginningOfGame;
446 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
447 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
448 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
449 int hiddenThinkOutputState = 0; /* [AS] */
450 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
451 int adjudicateLossPlies = 6;
452 char white_holding[64], black_holding[64];
453 TimeMark lastNodeCountTime;
454 long lastNodeCount=0;
455 int shiftKey; // [HGM] set by mouse handler
456
457 int have_sent_ICS_logon = 0;
458 int movesPerSession;
459 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
460 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
461 long timeControl_2; /* [AS] Allow separate time controls */
462 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
463 long timeRemaining[2][MAX_MOVES];
464 int matchGame = 0;
465 TimeMark programStartTime;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
468
469 /* animateTraining preserves the state of appData.animate
470  * when Training mode is activated. This allows the
471  * response to be animated when appData.animate == TRUE and
472  * appData.animateDragging == TRUE.
473  */
474 Boolean animateTraining;
475
476 GameInfo gameInfo;
477
478 AppData appData;
479
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char  castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char  initialRights[BOARD_FILES];
484 int   nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int   initialRulePlies, FENrulePlies;
486 FILE  *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 int loadFlag = 0;
488 int shuffleOpenings;
489 int mute; // mute all sounds
490
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int storedGames = 0;
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
500
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void CleanupTail P((void));
504
505 ChessSquare  FIDEArray[2][BOARD_FILES] = {
506     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509         BlackKing, BlackBishop, BlackKnight, BlackRook }
510 };
511
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514         WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515     { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516         BlackKing, BlackKing, BlackKnight, BlackRook }
517 };
518
519 ChessSquare  KnightmateArray[2][BOARD_FILES] = {
520     { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521         WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522     { BlackRook, BlackMan, BlackBishop, BlackQueen,
523         BlackUnicorn, BlackBishop, BlackMan, BlackRook }
524 };
525
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529     { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530         BlackDragon, BlackKing, BlackAngel, BlackAlfil }
531 };
532
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536     { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537         BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
538 };
539
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542         WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543     { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544         BlackFerz, BlackAlfil, BlackKnight, BlackRook }
545 };
546
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548     { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549         WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550     { BlackRook, BlackKnight, BlackMan, BlackFerz,
551         BlackKing, BlackMan, BlackKnight, BlackRook }
552 };
553
554
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
561 };
562
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
568 };
569
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
575 };
576
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
582 };
583
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
589 };
590
591 #ifdef GOTHIC
592 ChessSquare GothicArray[2][BOARD_FILES] = {
593     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
594         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
595     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
596         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
597 };
598 #else // !GOTHIC
599 #define GothicArray CapablancaArray
600 #endif // !GOTHIC
601
602 #ifdef FALCON
603 ChessSquare FalconArray[2][BOARD_FILES] = {
604     { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
605         WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
606     { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
607         BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
608 };
609 #else // !FALCON
610 #define FalconArray CapablancaArray
611 #endif // !FALCON
612
613 #else // !(BOARD_FILES>=10)
614 #define XiangqiPosition FIDEArray
615 #define CapablancaArray FIDEArray
616 #define GothicArray FIDEArray
617 #define GreatArray FIDEArray
618 #endif // !(BOARD_FILES>=10)
619
620 #if (BOARD_FILES>=12)
621 ChessSquare CourierArray[2][BOARD_FILES] = {
622     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
623         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
624     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
625         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
626 };
627 #else // !(BOARD_FILES>=12)
628 #define CourierArray CapablancaArray
629 #endif // !(BOARD_FILES>=12)
630
631
632 Board initialPosition;
633
634
635 /* Convert str to a rating. Checks for special cases of "----",
636
637    "++++", etc. Also strips ()'s */
638 int
639 string_to_rating(str)
640   char *str;
641 {
642   while(*str && !isdigit(*str)) ++str;
643   if (!*str)
644     return 0;   /* One of the special "no rating" cases */
645   else
646     return atoi(str);
647 }
648
649 void
650 ClearProgramStats()
651 {
652     /* Init programStats */
653     programStats.movelist[0] = 0;
654     programStats.depth = 0;
655     programStats.nr_moves = 0;
656     programStats.moves_left = 0;
657     programStats.nodes = 0;
658     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
659     programStats.score = 0;
660     programStats.got_only_move = 0;
661     programStats.got_fail = 0;
662     programStats.line_is_book = 0;
663 }
664
665 void
666 CommonEngineInit()
667 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
668     if (appData.firstPlaysBlack) {
669         first.twoMachinesColor = "black\n";
670         second.twoMachinesColor = "white\n";
671     } else {
672         first.twoMachinesColor = "white\n";
673         second.twoMachinesColor = "black\n";
674     }
675
676     first.other = &second;
677     second.other = &first;
678
679     { float norm = 1;
680         if(appData.timeOddsMode) {
681             norm = appData.timeOdds[0];
682             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
683         }
684         first.timeOdds  = appData.timeOdds[0]/norm;
685         second.timeOdds = appData.timeOdds[1]/norm;
686     }
687
688     if(programVersion) free(programVersion);
689     if (appData.noChessProgram) {
690         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
691         sprintf(programVersion, "%s", PACKAGE_STRING);
692     } else {
693       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
694       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
695       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
696     }
697 }
698
699 void
700 UnloadEngine(ChessProgramState *cps)
701 {
702         /* Kill off first chess program */
703         if (cps->isr != NULL)
704           RemoveInputSource(cps->isr);
705         cps->isr = NULL;
706
707         if (cps->pr != NoProc) {
708             ExitAnalyzeMode();
709             DoSleep( appData.delayBeforeQuit );
710             SendToProgram("quit\n", cps);
711             DoSleep( appData.delayAfterQuit );
712             DestroyChildProcess(cps->pr, cps->useSigterm);
713         }
714         cps->pr = NoProc;
715 }
716
717 void
718 ClearOptions(ChessProgramState *cps)
719 {
720     int i;
721     cps->nrOptions = cps->comboCnt = 0;
722     for(i=0; i<MAX_OPTIONS; i++) {
723         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
724         cps->option[i].textValue = 0;
725     }
726 }
727
728 char *engineNames[] = {
729 "first",
730 "second"
731 };
732
733 InitEngine(ChessProgramState *cps, int n)
734 {   // [HGM] all engine initialiation put in a function that does one engine
735
736     ClearOptions(cps);
737
738     cps->which = engineNames[n];
739     cps->maybeThinking = FALSE;
740     cps->pr = NoProc;
741     cps->isr = NULL;
742     cps->sendTime = 2;
743     cps->sendDrawOffers = 1;
744
745     cps->program = appData.chessProgram[n];
746     cps->host = appData.host[n];
747     cps->dir = appData.directory[n];
748     cps->initString = appData.engInitString[n];
749     cps->computerString = appData.computerString[n];
750     cps->useSigint  = TRUE;
751     cps->useSigterm = TRUE;
752     cps->reuse = appData.reuse[n];
753     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
754     cps->useSetboard = FALSE;
755     cps->useSAN = FALSE;
756     cps->usePing = FALSE;
757     cps->lastPing = 0;
758     cps->lastPong = 0;
759     cps->usePlayother = FALSE;
760     cps->useColors = TRUE;
761     cps->useUsermove = FALSE;
762     cps->sendICS = FALSE;
763     cps->sendName = appData.icsActive;
764     cps->sdKludge = FALSE;
765     cps->stKludge = FALSE;
766     TidyProgramName(cps->program, cps->host, cps->tidy);
767     cps->matchWins = 0;
768     safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
769     cps->analysisSupport = 2; /* detect */
770     cps->analyzing = FALSE;
771     cps->initDone = FALSE;
772
773     /* New features added by Tord: */
774     cps->useFEN960 = FALSE;
775     cps->useOOCastle = TRUE;
776     /* End of new features added by Tord. */
777     cps->fenOverride  = appData.fenOverride[n];
778
779     /* [HGM] time odds: set factor for each machine */
780     cps->timeOdds  = appData.timeOdds[n];
781
782     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
783     cps->accumulateTC = appData.accumulateTC[n];
784     cps->maxNrOfSessions = 1;
785
786     /* [HGM] debug */
787     cps->debug = FALSE;
788     cps->supportsNPS = UNKNOWN;
789
790     /* [HGM] options */
791     cps->optionSettings  = appData.engOptions[n];
792
793     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
794     cps->isUCI = appData.isUCI[n]; /* [AS] */
795     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
796
797     if (appData.protocolVersion[n] > PROTOVER
798         || appData.protocolVersion[n] < 1)
799       {
800         char buf[MSG_SIZ];
801         int len;
802
803         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
804                        appData.protocolVersion[n]);
805         if( (len > MSG_SIZ) && appData.debugMode )
806           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
807
808         DisplayFatalError(buf, 0, 2);
809       }
810     else
811       {
812         cps->protocolVersion = appData.protocolVersion[n];
813       }
814
815     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
816 }
817
818 ChessProgramState *savCps;
819
820 void
821 LoadEngine()
822 {
823     int i;
824     if(WaitForEngine(savCps, LoadEngine)) return;
825     CommonEngineInit(); // recalculate time odds
826     if(gameInfo.variant != StringToVariant(appData.variant)) {
827         // we changed variant when loading the engine; this forces us to reset
828         Reset(TRUE, savCps != &first);
829         EditGameEvent(); // for consistency with other path, as Reset changes mode
830     }
831     InitChessProgram(savCps, FALSE);
832     SendToProgram("force\n", savCps);
833     DisplayMessage("", "");
834     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
835     for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
836     ThawUI();
837     SetGNUMode();
838 }
839
840 void
841 ReplaceEngine(ChessProgramState *cps, int n)
842 {
843     EditGameEvent();
844     UnloadEngine(cps);
845     appData.noChessProgram = False;
846     appData.clockMode = True;
847     InitEngine(cps, n);
848     if(n) return; // only startup first engine immediately; second can wait
849     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
850     LoadEngine();
851 }
852
853 void
854 InitBackEnd1()
855 {
856     int matched, min, sec;
857
858     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
859     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
860
861     GetTimeMark(&programStartTime);
862     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
863
864     ClearProgramStats();
865     programStats.ok_to_send = 1;
866     programStats.seen_stat = 0;
867
868     /*
869      * Initialize game list
870      */
871     ListNew(&gameList);
872
873
874     /*
875      * Internet chess server status
876      */
877     if (appData.icsActive) {
878         appData.matchMode = FALSE;
879         appData.matchGames = 0;
880 #if ZIPPY
881         appData.noChessProgram = !appData.zippyPlay;
882 #else
883         appData.zippyPlay = FALSE;
884         appData.zippyTalk = FALSE;
885         appData.noChessProgram = TRUE;
886 #endif
887         if (*appData.icsHelper != NULLCHAR) {
888             appData.useTelnet = TRUE;
889             appData.telnetProgram = appData.icsHelper;
890         }
891     } else {
892         appData.zippyTalk = appData.zippyPlay = FALSE;
893     }
894
895     /* [AS] Initialize pv info list [HGM] and game state */
896     {
897         int i, j;
898
899         for( i=0; i<=framePtr; i++ ) {
900             pvInfoList[i].depth = -1;
901             boards[i][EP_STATUS] = EP_NONE;
902             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
903         }
904     }
905
906     /*
907      * Parse timeControl resource
908      */
909     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
910                           appData.movesPerSession)) {
911         char buf[MSG_SIZ];
912         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
913         DisplayFatalError(buf, 0, 2);
914     }
915
916     /*
917      * Parse searchTime resource
918      */
919     if (*appData.searchTime != NULLCHAR) {
920         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
921         if (matched == 1) {
922             searchTime = min * 60;
923         } else if (matched == 2) {
924             searchTime = min * 60 + sec;
925         } else {
926             char buf[MSG_SIZ];
927             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
928             DisplayFatalError(buf, 0, 2);
929         }
930     }
931
932     /* [AS] Adjudication threshold */
933     adjudicateLossThreshold = appData.adjudicateLossThreshold;
934
935     InitEngine(&first, 0);
936     InitEngine(&second, 1);
937     CommonEngineInit();
938
939     if (appData.icsActive) {
940         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
941     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
942         appData.clockMode = FALSE;
943         first.sendTime = second.sendTime = 0;
944     }
945
946 #if ZIPPY
947     /* Override some settings from environment variables, for backward
948        compatibility.  Unfortunately it's not feasible to have the env
949        vars just set defaults, at least in xboard.  Ugh.
950     */
951     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
952       ZippyInit();
953     }
954 #endif
955
956     if (!appData.icsActive) {
957       char buf[MSG_SIZ];
958       int len;
959
960       /* Check for variants that are supported only in ICS mode,
961          or not at all.  Some that are accepted here nevertheless
962          have bugs; see comments below.
963       */
964       VariantClass variant = StringToVariant(appData.variant);
965       switch (variant) {
966       case VariantBughouse:     /* need four players and two boards */
967       case VariantKriegspiel:   /* need to hide pieces and move details */
968         /* case VariantFischeRandom: (Fabien: moved below) */
969         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
970         if( (len > MSG_SIZ) && appData.debugMode )
971           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
972
973         DisplayFatalError(buf, 0, 2);
974         return;
975
976       case VariantUnknown:
977       case VariantLoadable:
978       case Variant29:
979       case Variant30:
980       case Variant31:
981       case Variant32:
982       case Variant33:
983       case Variant34:
984       case Variant35:
985       case Variant36:
986       default:
987         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
988         if( (len > MSG_SIZ) && appData.debugMode )
989           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
990
991         DisplayFatalError(buf, 0, 2);
992         return;
993
994       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
995       case VariantFairy:      /* [HGM] TestLegality definitely off! */
996       case VariantGothic:     /* [HGM] should work */
997       case VariantCapablanca: /* [HGM] should work */
998       case VariantCourier:    /* [HGM] initial forced moves not implemented */
999       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1000       case VariantKnightmate: /* [HGM] should work */
1001       case VariantCylinder:   /* [HGM] untested */
1002       case VariantFalcon:     /* [HGM] untested */
1003       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1004                                  offboard interposition not understood */
1005       case VariantNormal:     /* definitely works! */
1006       case VariantWildCastle: /* pieces not automatically shuffled */
1007       case VariantNoCastle:   /* pieces not automatically shuffled */
1008       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1009       case VariantLosers:     /* should work except for win condition,
1010                                  and doesn't know captures are mandatory */
1011       case VariantSuicide:    /* should work except for win condition,
1012                                  and doesn't know captures are mandatory */
1013       case VariantGiveaway:   /* should work except for win condition,
1014                                  and doesn't know captures are mandatory */
1015       case VariantTwoKings:   /* should work */
1016       case VariantAtomic:     /* should work except for win condition */
1017       case Variant3Check:     /* should work except for win condition */
1018       case VariantShatranj:   /* should work except for all win conditions */
1019       case VariantMakruk:     /* should work except for daw countdown */
1020       case VariantBerolina:   /* might work if TestLegality is off */
1021       case VariantCapaRandom: /* should work */
1022       case VariantJanus:      /* should work */
1023       case VariantSuper:      /* experimental */
1024       case VariantGreat:      /* experimental, requires legality testing to be off */
1025       case VariantSChess:     /* S-Chess, should work */
1026       case VariantSpartan:    /* should work */
1027         break;
1028       }
1029     }
1030
1031 }
1032
1033 int NextIntegerFromString( char ** str, long * value )
1034 {
1035     int result = -1;
1036     char * s = *str;
1037
1038     while( *s == ' ' || *s == '\t' ) {
1039         s++;
1040     }
1041
1042     *value = 0;
1043
1044     if( *s >= '0' && *s <= '9' ) {
1045         while( *s >= '0' && *s <= '9' ) {
1046             *value = *value * 10 + (*s - '0');
1047             s++;
1048         }
1049
1050         result = 0;
1051     }
1052
1053     *str = s;
1054
1055     return result;
1056 }
1057
1058 int NextTimeControlFromString( char ** str, long * value )
1059 {
1060     long temp;
1061     int result = NextIntegerFromString( str, &temp );
1062
1063     if( result == 0 ) {
1064         *value = temp * 60; /* Minutes */
1065         if( **str == ':' ) {
1066             (*str)++;
1067             result = NextIntegerFromString( str, &temp );
1068             *value += temp; /* Seconds */
1069         }
1070     }
1071
1072     return result;
1073 }
1074
1075 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1076 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1077     int result = -1, type = 0; long temp, temp2;
1078
1079     if(**str != ':') return -1; // old params remain in force!
1080     (*str)++;
1081     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1082     if( NextIntegerFromString( str, &temp ) ) return -1;
1083     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1084
1085     if(**str != '/') {
1086         /* time only: incremental or sudden-death time control */
1087         if(**str == '+') { /* increment follows; read it */
1088             (*str)++;
1089             if(**str == '!') type = *(*str)++; // Bronstein TC
1090             if(result = NextIntegerFromString( str, &temp2)) return -1;
1091             *inc = temp2 * 1000;
1092             if(**str == '.') { // read fraction of increment
1093                 char *start = ++(*str);
1094                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1095                 temp2 *= 1000;
1096                 while(start++ < *str) temp2 /= 10;
1097                 *inc += temp2;
1098             }
1099         } else *inc = 0;
1100         *moves = 0; *tc = temp * 1000; *incType = type;
1101         return 0;
1102     }
1103
1104     (*str)++; /* classical time control */
1105     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1106
1107     if(result == 0) {
1108         *moves = temp;
1109         *tc    = temp2 * 1000;
1110         *inc   = 0;
1111         *incType = type;
1112     }
1113     return result;
1114 }
1115
1116 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1117 {   /* [HGM] get time to add from the multi-session time-control string */
1118     int incType, moves=1; /* kludge to force reading of first session */
1119     long time, increment;
1120     char *s = tcString;
1121
1122     if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1123     if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1124     do {
1125         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1126         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1127         if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1128         if(movenr == -1) return time;    /* last move before new session     */
1129         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1130         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1131         if(!moves) return increment;     /* current session is incremental   */
1132         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1133     } while(movenr >= -1);               /* try again for next session       */
1134
1135     return 0; // no new time quota on this move
1136 }
1137
1138 int
1139 ParseTimeControl(tc, ti, mps)
1140      char *tc;
1141      float ti;
1142      int mps;
1143 {
1144   long tc1;
1145   long tc2;
1146   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1147   int min, sec=0;
1148
1149   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1150   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1151       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1152   if(ti > 0) {
1153
1154     if(mps)
1155       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1156     else 
1157       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1158   } else {
1159     if(mps)
1160       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1161     else 
1162       snprintf(buf, MSG_SIZ, ":%s", mytc);
1163   }
1164   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1165   
1166   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1167     return FALSE;
1168   }
1169
1170   if( *tc == '/' ) {
1171     /* Parse second time control */
1172     tc++;
1173
1174     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1175       return FALSE;
1176     }
1177
1178     if( tc2 == 0 ) {
1179       return FALSE;
1180     }
1181
1182     timeControl_2 = tc2 * 1000;
1183   }
1184   else {
1185     timeControl_2 = 0;
1186   }
1187
1188   if( tc1 == 0 ) {
1189     return FALSE;
1190   }
1191
1192   timeControl = tc1 * 1000;
1193
1194   if (ti >= 0) {
1195     timeIncrement = ti * 1000;  /* convert to ms */
1196     movesPerSession = 0;
1197   } else {
1198     timeIncrement = 0;
1199     movesPerSession = mps;
1200   }
1201   return TRUE;
1202 }
1203
1204 void
1205 InitBackEnd2()
1206 {
1207     if (appData.debugMode) {
1208         fprintf(debugFP, "%s\n", programVersion);
1209     }
1210
1211     set_cont_sequence(appData.wrapContSeq);
1212     if (appData.matchGames > 0) {
1213         appData.matchMode = TRUE;
1214     } else if (appData.matchMode) {
1215         appData.matchGames = 1;
1216     }
1217     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1218         appData.matchGames = appData.sameColorGames;
1219     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1220         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1221         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1222     }
1223     Reset(TRUE, FALSE);
1224     if (appData.noChessProgram || first.protocolVersion == 1) {
1225       InitBackEnd3();
1226     } else {
1227       /* kludge: allow timeout for initial "feature" commands */
1228       FreezeUI();
1229       DisplayMessage("", _("Starting chess program"));
1230       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1231     }
1232 }
1233
1234 int
1235 CalculateIndex(int index, int gameNr)
1236 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1237     int res;
1238     if(index > 0) return index; // fixed nmber
1239     if(index == 0) return 1;
1240     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1241     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1242     return res;
1243 }
1244
1245 int
1246 LoadGameOrPosition(int gameNr)
1247 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1248     if (*appData.loadGameFile != NULLCHAR) {
1249         if (!LoadGameFromFile(appData.loadGameFile,
1250                 CalculateIndex(appData.loadGameIndex, gameNr),
1251                               appData.loadGameFile, FALSE)) {
1252             DisplayFatalError(_("Bad game file"), 0, 1);
1253             return 0;
1254         }
1255     } else if (*appData.loadPositionFile != NULLCHAR) {
1256         if (!LoadPositionFromFile(appData.loadPositionFile,
1257                 CalculateIndex(appData.loadPositionIndex, gameNr),
1258                                   appData.loadPositionFile)) {
1259             DisplayFatalError(_("Bad position file"), 0, 1);
1260             return 0;
1261         }
1262     }
1263     return 1;
1264 }
1265
1266 void
1267 MatchEvent(int mode)
1268 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1269         /* Set up machine vs. machine match */
1270         if (appData.noChessProgram) {
1271             DisplayFatalError(_("Can't have a match with no chess programs"),
1272                               0, 2);
1273             return;
1274         }
1275         matchMode = mode;
1276         matchGame = 1;
1277         if(!LoadGameOrPositionFile(1)) return;
1278         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches\r
1279         TwoMachinesEvent();
1280 }
1281
1282 void
1283 InitBackEnd3 P((void))
1284 {
1285     GameMode initialMode;
1286     char buf[MSG_SIZ];
1287     int err, len;
1288
1289     InitChessProgram(&first, startedFromSetupPosition);
1290
1291     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1292         free(programVersion);
1293         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1294         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1295     }
1296
1297     if (appData.icsActive) {
1298 #ifdef WIN32
1299         /* [DM] Make a console window if needed [HGM] merged ifs */
1300         ConsoleCreate();
1301 #endif
1302         err = establish();
1303         if (err != 0)
1304           {
1305             if (*appData.icsCommPort != NULLCHAR)
1306               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1307                              appData.icsCommPort);
1308             else
1309               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1310                         appData.icsHost, appData.icsPort);
1311
1312             if( (len > MSG_SIZ) && appData.debugMode )
1313               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1314
1315             DisplayFatalError(buf, err, 1);
1316             return;
1317         }
1318         SetICSMode();
1319         telnetISR =
1320           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1321         fromUserISR =
1322           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1323         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1324             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1325     } else if (appData.noChessProgram) {
1326         SetNCPMode();
1327     } else {
1328         SetGNUMode();
1329     }
1330
1331     if (*appData.cmailGameName != NULLCHAR) {
1332         SetCmailMode();
1333         OpenLoopback(&cmailPR);
1334         cmailISR =
1335           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1336     }
1337
1338     ThawUI();
1339     DisplayMessage("", "");
1340     if (StrCaseCmp(appData.initialMode, "") == 0) {
1341       initialMode = BeginningOfGame;
1342       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1343         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1344         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1345         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1346         ModeHighlight();
1347       }
1348     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1349       initialMode = TwoMachinesPlay;
1350     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1351       initialMode = AnalyzeFile;
1352     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1353       initialMode = AnalyzeMode;
1354     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1355       initialMode = MachinePlaysWhite;
1356     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1357       initialMode = MachinePlaysBlack;
1358     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1359       initialMode = EditGame;
1360     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1361       initialMode = EditPosition;
1362     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1363       initialMode = Training;
1364     } else {
1365       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1366       if( (len > MSG_SIZ) && appData.debugMode )
1367         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1368
1369       DisplayFatalError(buf, 0, 2);
1370       return;
1371     }
1372
1373     if (appData.matchMode) {
1374         MatchEvent(TRUE);
1375     } else if (*appData.cmailGameName != NULLCHAR) {
1376         /* Set up cmail mode */
1377         ReloadCmailMsgEvent(TRUE);
1378     } else {
1379         /* Set up other modes */
1380         if (initialMode == AnalyzeFile) {
1381           if (*appData.loadGameFile == NULLCHAR) {
1382             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1383             return;
1384           }
1385         }
1386         if (*appData.loadGameFile != NULLCHAR) {
1387             (void) LoadGameFromFile(appData.loadGameFile,
1388                                     appData.loadGameIndex,
1389                                     appData.loadGameFile, TRUE);
1390         } else if (*appData.loadPositionFile != NULLCHAR) {
1391             (void) LoadPositionFromFile(appData.loadPositionFile,
1392                                         appData.loadPositionIndex,
1393                                         appData.loadPositionFile);
1394             /* [HGM] try to make self-starting even after FEN load */
1395             /* to allow automatic setup of fairy variants with wtm */
1396             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1397                 gameMode = BeginningOfGame;
1398                 setboardSpoiledMachineBlack = 1;
1399             }
1400             /* [HGM] loadPos: make that every new game uses the setup */
1401             /* from file as long as we do not switch variant          */
1402             if(!blackPlaysFirst) {
1403                 startedFromPositionFile = TRUE;
1404                 CopyBoard(filePosition, boards[0]);
1405             }
1406         }
1407         if (initialMode == AnalyzeMode) {
1408           if (appData.noChessProgram) {
1409             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1410             return;
1411           }
1412           if (appData.icsActive) {
1413             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1414             return;
1415           }
1416           AnalyzeModeEvent();
1417         } else if (initialMode == AnalyzeFile) {
1418           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1419           ShowThinkingEvent();
1420           AnalyzeFileEvent();
1421           AnalysisPeriodicEvent(1);
1422         } else if (initialMode == MachinePlaysWhite) {
1423           if (appData.noChessProgram) {
1424             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1425                               0, 2);
1426             return;
1427           }
1428           if (appData.icsActive) {
1429             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1430                               0, 2);
1431             return;
1432           }
1433           MachineWhiteEvent();
1434         } else if (initialMode == MachinePlaysBlack) {
1435           if (appData.noChessProgram) {
1436             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1437                               0, 2);
1438             return;
1439           }
1440           if (appData.icsActive) {
1441             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1442                               0, 2);
1443             return;
1444           }
1445           MachineBlackEvent();
1446         } else if (initialMode == TwoMachinesPlay) {
1447           if (appData.noChessProgram) {
1448             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1449                               0, 2);
1450             return;
1451           }
1452           if (appData.icsActive) {
1453             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1454                               0, 2);
1455             return;
1456           }
1457           TwoMachinesEvent();
1458         } else if (initialMode == EditGame) {
1459           EditGameEvent();
1460         } else if (initialMode == EditPosition) {
1461           EditPositionEvent();
1462         } else if (initialMode == Training) {
1463           if (*appData.loadGameFile == NULLCHAR) {
1464             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1465             return;
1466           }
1467           TrainingEvent();
1468         }
1469     }
1470 }
1471
1472 /*
1473  * Establish will establish a contact to a remote host.port.
1474  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1475  *  used to talk to the host.
1476  * Returns 0 if okay, error code if not.
1477  */
1478 int
1479 establish()
1480 {
1481     char buf[MSG_SIZ];
1482
1483     if (*appData.icsCommPort != NULLCHAR) {
1484         /* Talk to the host through a serial comm port */
1485         return OpenCommPort(appData.icsCommPort, &icsPR);
1486
1487     } else if (*appData.gateway != NULLCHAR) {
1488         if (*appData.remoteShell == NULLCHAR) {
1489             /* Use the rcmd protocol to run telnet program on a gateway host */
1490             snprintf(buf, sizeof(buf), "%s %s %s",
1491                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1492             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1493
1494         } else {
1495             /* Use the rsh program to run telnet program on a gateway host */
1496             if (*appData.remoteUser == NULLCHAR) {
1497                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1498                         appData.gateway, appData.telnetProgram,
1499                         appData.icsHost, appData.icsPort);
1500             } else {
1501                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1502                         appData.remoteShell, appData.gateway,
1503                         appData.remoteUser, appData.telnetProgram,
1504                         appData.icsHost, appData.icsPort);
1505             }
1506             return StartChildProcess(buf, "", &icsPR);
1507
1508         }
1509     } else if (appData.useTelnet) {
1510         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1511
1512     } else {
1513         /* TCP socket interface differs somewhat between
1514            Unix and NT; handle details in the front end.
1515            */
1516         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1517     }
1518 }
1519
1520 void EscapeExpand(char *p, char *q)
1521 {       // [HGM] initstring: routine to shape up string arguments
1522         while(*p++ = *q++) if(p[-1] == '\\')
1523             switch(*q++) {
1524                 case 'n': p[-1] = '\n'; break;
1525                 case 'r': p[-1] = '\r'; break;
1526                 case 't': p[-1] = '\t'; break;
1527                 case '\\': p[-1] = '\\'; break;
1528                 case 0: *p = 0; return;
1529                 default: p[-1] = q[-1]; break;
1530             }
1531 }
1532
1533 void
1534 show_bytes(fp, buf, count)
1535      FILE *fp;
1536      char *buf;
1537      int count;
1538 {
1539     while (count--) {
1540         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1541             fprintf(fp, "\\%03o", *buf & 0xff);
1542         } else {
1543             putc(*buf, fp);
1544         }
1545         buf++;
1546     }
1547     fflush(fp);
1548 }
1549
1550 /* Returns an errno value */
1551 int
1552 OutputMaybeTelnet(pr, message, count, outError)
1553      ProcRef pr;
1554      char *message;
1555      int count;
1556      int *outError;
1557 {
1558     char buf[8192], *p, *q, *buflim;
1559     int left, newcount, outcount;
1560
1561     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1562         *appData.gateway != NULLCHAR) {
1563         if (appData.debugMode) {
1564             fprintf(debugFP, ">ICS: ");
1565             show_bytes(debugFP, message, count);
1566             fprintf(debugFP, "\n");
1567         }
1568         return OutputToProcess(pr, message, count, outError);
1569     }
1570
1571     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1572     p = message;
1573     q = buf;
1574     left = count;
1575     newcount = 0;
1576     while (left) {
1577         if (q >= buflim) {
1578             if (appData.debugMode) {
1579                 fprintf(debugFP, ">ICS: ");
1580                 show_bytes(debugFP, buf, newcount);
1581                 fprintf(debugFP, "\n");
1582             }
1583             outcount = OutputToProcess(pr, buf, newcount, outError);
1584             if (outcount < newcount) return -1; /* to be sure */
1585             q = buf;
1586             newcount = 0;
1587         }
1588         if (*p == '\n') {
1589             *q++ = '\r';
1590             newcount++;
1591         } else if (((unsigned char) *p) == TN_IAC) {
1592             *q++ = (char) TN_IAC;
1593             newcount ++;
1594         }
1595         *q++ = *p++;
1596         newcount++;
1597         left--;
1598     }
1599     if (appData.debugMode) {
1600         fprintf(debugFP, ">ICS: ");
1601         show_bytes(debugFP, buf, newcount);
1602         fprintf(debugFP, "\n");
1603     }
1604     outcount = OutputToProcess(pr, buf, newcount, outError);
1605     if (outcount < newcount) return -1; /* to be sure */
1606     return count;
1607 }
1608
1609 void
1610 read_from_player(isr, closure, message, count, error)
1611      InputSourceRef isr;
1612      VOIDSTAR closure;
1613      char *message;
1614      int count;
1615      int error;
1616 {
1617     int outError, outCount;
1618     static int gotEof = 0;
1619
1620     /* Pass data read from player on to ICS */
1621     if (count > 0) {
1622         gotEof = 0;
1623         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1624         if (outCount < count) {
1625             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1626         }
1627     } else if (count < 0) {
1628         RemoveInputSource(isr);
1629         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1630     } else if (gotEof++ > 0) {
1631         RemoveInputSource(isr);
1632         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1633     }
1634 }
1635
1636 void
1637 KeepAlive()
1638 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1639     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1640     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1641     SendToICS("date\n");
1642     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1643 }
1644
1645 /* added routine for printf style output to ics */
1646 void ics_printf(char *format, ...)
1647 {
1648     char buffer[MSG_SIZ];
1649     va_list args;
1650
1651     va_start(args, format);
1652     vsnprintf(buffer, sizeof(buffer), format, args);
1653     buffer[sizeof(buffer)-1] = '\0';
1654     SendToICS(buffer);
1655     va_end(args);
1656 }
1657
1658 void
1659 SendToICS(s)
1660      char *s;
1661 {
1662     int count, outCount, outError;
1663
1664     if (icsPR == NULL) return;
1665
1666     count = strlen(s);
1667     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1668     if (outCount < count) {
1669         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1670     }
1671 }
1672
1673 /* This is used for sending logon scripts to the ICS. Sending
1674    without a delay causes problems when using timestamp on ICC
1675    (at least on my machine). */
1676 void
1677 SendToICSDelayed(s,msdelay)
1678      char *s;
1679      long msdelay;
1680 {
1681     int count, outCount, outError;
1682
1683     if (icsPR == NULL) return;
1684
1685     count = strlen(s);
1686     if (appData.debugMode) {
1687         fprintf(debugFP, ">ICS: ");
1688         show_bytes(debugFP, s, count);
1689         fprintf(debugFP, "\n");
1690     }
1691     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1692                                       msdelay);
1693     if (outCount < count) {
1694         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1695     }
1696 }
1697
1698
1699 /* Remove all highlighting escape sequences in s
1700    Also deletes any suffix starting with '('
1701    */
1702 char *
1703 StripHighlightAndTitle(s)
1704      char *s;
1705 {
1706     static char retbuf[MSG_SIZ];
1707     char *p = retbuf;
1708
1709     while (*s != NULLCHAR) {
1710         while (*s == '\033') {
1711             while (*s != NULLCHAR && !isalpha(*s)) s++;
1712             if (*s != NULLCHAR) s++;
1713         }
1714         while (*s != NULLCHAR && *s != '\033') {
1715             if (*s == '(' || *s == '[') {
1716                 *p = NULLCHAR;
1717                 return retbuf;
1718             }
1719             *p++ = *s++;
1720         }
1721     }
1722     *p = NULLCHAR;
1723     return retbuf;
1724 }
1725
1726 /* Remove all highlighting escape sequences in s */
1727 char *
1728 StripHighlight(s)
1729      char *s;
1730 {
1731     static char retbuf[MSG_SIZ];
1732     char *p = retbuf;
1733
1734     while (*s != NULLCHAR) {
1735         while (*s == '\033') {
1736             while (*s != NULLCHAR && !isalpha(*s)) s++;
1737             if (*s != NULLCHAR) s++;
1738         }
1739         while (*s != NULLCHAR && *s != '\033') {
1740             *p++ = *s++;
1741         }
1742     }
1743     *p = NULLCHAR;
1744     return retbuf;
1745 }
1746
1747 char *variantNames[] = VARIANT_NAMES;
1748 char *
1749 VariantName(v)
1750      VariantClass v;
1751 {
1752     return variantNames[v];
1753 }
1754
1755
1756 /* Identify a variant from the strings the chess servers use or the
1757    PGN Variant tag names we use. */
1758 VariantClass
1759 StringToVariant(e)
1760      char *e;
1761 {
1762     char *p;
1763     int wnum = -1;
1764     VariantClass v = VariantNormal;
1765     int i, found = FALSE;
1766     char buf[MSG_SIZ];
1767     int len;
1768
1769     if (!e) return v;
1770
1771     /* [HGM] skip over optional board-size prefixes */
1772     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1773         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1774         while( *e++ != '_');
1775     }
1776
1777     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1778         v = VariantNormal;
1779         found = TRUE;
1780     } else
1781     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1782       if (StrCaseStr(e, variantNames[i])) {
1783         v = (VariantClass) i;
1784         found = TRUE;
1785         break;
1786       }
1787     }
1788
1789     if (!found) {
1790       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1791           || StrCaseStr(e, "wild/fr")
1792           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1793         v = VariantFischeRandom;
1794       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1795                  (i = 1, p = StrCaseStr(e, "w"))) {
1796         p += i;
1797         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1798         if (isdigit(*p)) {
1799           wnum = atoi(p);
1800         } else {
1801           wnum = -1;
1802         }
1803         switch (wnum) {
1804         case 0: /* FICS only, actually */
1805         case 1:
1806           /* Castling legal even if K starts on d-file */
1807           v = VariantWildCastle;
1808           break;
1809         case 2:
1810         case 3:
1811         case 4:
1812           /* Castling illegal even if K & R happen to start in
1813              normal positions. */
1814           v = VariantNoCastle;
1815           break;
1816         case 5:
1817         case 7:
1818         case 8:
1819         case 10:
1820         case 11:
1821         case 12:
1822         case 13:
1823         case 14:
1824         case 15:
1825         case 18:
1826         case 19:
1827           /* Castling legal iff K & R start in normal positions */
1828           v = VariantNormal;
1829           break;
1830         case 6:
1831         case 20:
1832         case 21:
1833           /* Special wilds for position setup; unclear what to do here */
1834           v = VariantLoadable;
1835           break;
1836         case 9:
1837           /* Bizarre ICC game */
1838           v = VariantTwoKings;
1839           break;
1840         case 16:
1841           v = VariantKriegspiel;
1842           break;
1843         case 17:
1844           v = VariantLosers;
1845           break;
1846         case 22:
1847           v = VariantFischeRandom;
1848           break;
1849         case 23:
1850           v = VariantCrazyhouse;
1851           break;
1852         case 24:
1853           v = VariantBughouse;
1854           break;
1855         case 25:
1856           v = Variant3Check;
1857           break;
1858         case 26:
1859           /* Not quite the same as FICS suicide! */
1860           v = VariantGiveaway;
1861           break;
1862         case 27:
1863           v = VariantAtomic;
1864           break;
1865         case 28:
1866           v = VariantShatranj;
1867           break;
1868
1869         /* Temporary names for future ICC types.  The name *will* change in
1870            the next xboard/WinBoard release after ICC defines it. */
1871         case 29:
1872           v = Variant29;
1873           break;
1874         case 30:
1875           v = Variant30;
1876           break;
1877         case 31:
1878           v = Variant31;
1879           break;
1880         case 32:
1881           v = Variant32;
1882           break;
1883         case 33:
1884           v = Variant33;
1885           break;
1886         case 34:
1887           v = Variant34;
1888           break;
1889         case 35:
1890           v = Variant35;
1891           break;
1892         case 36:
1893           v = Variant36;
1894           break;
1895         case 37:
1896           v = VariantShogi;
1897           break;
1898         case 38:
1899           v = VariantXiangqi;
1900           break;
1901         case 39:
1902           v = VariantCourier;
1903           break;
1904         case 40:
1905           v = VariantGothic;
1906           break;
1907         case 41:
1908           v = VariantCapablanca;
1909           break;
1910         case 42:
1911           v = VariantKnightmate;
1912           break;
1913         case 43:
1914           v = VariantFairy;
1915           break;
1916         case 44:
1917           v = VariantCylinder;
1918           break;
1919         case 45:
1920           v = VariantFalcon;
1921           break;
1922         case 46:
1923           v = VariantCapaRandom;
1924           break;
1925         case 47:
1926           v = VariantBerolina;
1927           break;
1928         case 48:
1929           v = VariantJanus;
1930           break;
1931         case 49:
1932           v = VariantSuper;
1933           break;
1934         case 50:
1935           v = VariantGreat;
1936           break;
1937         case -1:
1938           /* Found "wild" or "w" in the string but no number;
1939              must assume it's normal chess. */
1940           v = VariantNormal;
1941           break;
1942         default:
1943           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1944           if( (len > MSG_SIZ) && appData.debugMode )
1945             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1946
1947           DisplayError(buf, 0);
1948           v = VariantUnknown;
1949           break;
1950         }
1951       }
1952     }
1953     if (appData.debugMode) {
1954       fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1955               e, wnum, VariantName(v));
1956     }
1957     return v;
1958 }
1959
1960 static int leftover_start = 0, leftover_len = 0;
1961 char star_match[STAR_MATCH_N][MSG_SIZ];
1962
1963 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1964    advance *index beyond it, and set leftover_start to the new value of
1965    *index; else return FALSE.  If pattern contains the character '*', it
1966    matches any sequence of characters not containing '\r', '\n', or the
1967    character following the '*' (if any), and the matched sequence(s) are
1968    copied into star_match.
1969    */
1970 int
1971 looking_at(buf, index, pattern)
1972      char *buf;
1973      int *index;
1974      char *pattern;
1975 {
1976     char *bufp = &buf[*index], *patternp = pattern;
1977     int star_count = 0;
1978     char *matchp = star_match[0];
1979
1980     for (;;) {
1981         if (*patternp == NULLCHAR) {
1982             *index = leftover_start = bufp - buf;
1983             *matchp = NULLCHAR;
1984             return TRUE;
1985         }
1986         if (*bufp == NULLCHAR) return FALSE;
1987         if (*patternp == '*') {
1988             if (*bufp == *(patternp + 1)) {
1989                 *matchp = NULLCHAR;
1990                 matchp = star_match[++star_count];
1991                 patternp += 2;
1992                 bufp++;
1993                 continue;
1994             } else if (*bufp == '\n' || *bufp == '\r') {
1995                 patternp++;
1996                 if (*patternp == NULLCHAR)
1997                   continue;
1998                 else
1999                   return FALSE;
2000             } else {
2001                 *matchp++ = *bufp++;
2002                 continue;
2003             }
2004         }
2005         if (*patternp != *bufp) return FALSE;
2006         patternp++;
2007         bufp++;
2008     }
2009 }
2010
2011 void
2012 SendToPlayer(data, length)
2013      char *data;
2014      int length;
2015 {
2016     int error, outCount;
2017     outCount = OutputToProcess(NoProc, data, length, &error);
2018     if (outCount < length) {
2019         DisplayFatalError(_("Error writing to display"), error, 1);
2020     }
2021 }
2022
2023 void
2024 PackHolding(packed, holding)
2025      char packed[];
2026      char *holding;
2027 {
2028     char *p = holding;
2029     char *q = packed;
2030     int runlength = 0;
2031     int curr = 9999;
2032     do {
2033         if (*p == curr) {
2034             runlength++;
2035         } else {
2036             switch (runlength) {
2037               case 0:
2038                 break;
2039               case 1:
2040                 *q++ = curr;
2041                 break;
2042               case 2:
2043                 *q++ = curr;
2044                 *q++ = curr;
2045                 break;
2046               default:
2047                 sprintf(q, "%d", runlength);
2048                 while (*q) q++;
2049                 *q++ = curr;
2050                 break;
2051             }
2052             runlength = 1;
2053             curr = *p;
2054         }
2055     } while (*p++);
2056     *q = NULLCHAR;
2057 }
2058
2059 /* Telnet protocol requests from the front end */
2060 void
2061 TelnetRequest(ddww, option)
2062      unsigned char ddww, option;
2063 {
2064     unsigned char msg[3];
2065     int outCount, outError;
2066
2067     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2068
2069     if (appData.debugMode) {
2070         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2071         switch (ddww) {
2072           case TN_DO:
2073             ddwwStr = "DO";
2074             break;
2075           case TN_DONT:
2076             ddwwStr = "DONT";
2077             break;
2078           case TN_WILL:
2079             ddwwStr = "WILL";
2080             break;
2081           case TN_WONT:
2082             ddwwStr = "WONT";
2083             break;
2084           default:
2085             ddwwStr = buf1;
2086             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2087             break;
2088         }
2089         switch (option) {
2090           case TN_ECHO:
2091             optionStr = "ECHO";
2092             break;
2093           default:
2094             optionStr = buf2;
2095             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2096             break;
2097         }
2098         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2099     }
2100     msg[0] = TN_IAC;
2101     msg[1] = ddww;
2102     msg[2] = option;
2103     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2104     if (outCount < 3) {
2105         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2106     }
2107 }
2108
2109 void
2110 DoEcho()
2111 {
2112     if (!appData.icsActive) return;
2113     TelnetRequest(TN_DO, TN_ECHO);
2114 }
2115
2116 void
2117 DontEcho()
2118 {
2119     if (!appData.icsActive) return;
2120     TelnetRequest(TN_DONT, TN_ECHO);
2121 }
2122
2123 void
2124 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2125 {
2126     /* put the holdings sent to us by the server on the board holdings area */
2127     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2128     char p;
2129     ChessSquare piece;
2130
2131     if(gameInfo.holdingsWidth < 2)  return;
2132     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2133         return; // prevent overwriting by pre-board holdings
2134
2135     if( (int)lowestPiece >= BlackPawn ) {
2136         holdingsColumn = 0;
2137         countsColumn = 1;
2138         holdingsStartRow = BOARD_HEIGHT-1;
2139         direction = -1;
2140     } else {
2141         holdingsColumn = BOARD_WIDTH-1;
2142         countsColumn = BOARD_WIDTH-2;
2143         holdingsStartRow = 0;
2144         direction = 1;
2145     }
2146
2147     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2148         board[i][holdingsColumn] = EmptySquare;
2149         board[i][countsColumn]   = (ChessSquare) 0;
2150     }
2151     while( (p=*holdings++) != NULLCHAR ) {
2152         piece = CharToPiece( ToUpper(p) );
2153         if(piece == EmptySquare) continue;
2154         /*j = (int) piece - (int) WhitePawn;*/
2155         j = PieceToNumber(piece);
2156         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2157         if(j < 0) continue;               /* should not happen */
2158         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2159         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2160         board[holdingsStartRow+j*direction][countsColumn]++;
2161     }
2162 }
2163
2164
2165 void
2166 VariantSwitch(Board board, VariantClass newVariant)
2167 {
2168    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2169    static Board oldBoard;
2170
2171    startedFromPositionFile = FALSE;
2172    if(gameInfo.variant == newVariant) return;
2173
2174    /* [HGM] This routine is called each time an assignment is made to
2175     * gameInfo.variant during a game, to make sure the board sizes
2176     * are set to match the new variant. If that means adding or deleting
2177     * holdings, we shift the playing board accordingly
2178     * This kludge is needed because in ICS observe mode, we get boards
2179     * of an ongoing game without knowing the variant, and learn about the
2180     * latter only later. This can be because of the move list we requested,
2181     * in which case the game history is refilled from the beginning anyway,
2182     * but also when receiving holdings of a crazyhouse game. In the latter
2183     * case we want to add those holdings to the already received position.
2184     */
2185
2186
2187    if (appData.debugMode) {
2188      fprintf(debugFP, "Switch board from %s to %s\n",
2189              VariantName(gameInfo.variant), VariantName(newVariant));
2190      setbuf(debugFP, NULL);
2191    }
2192    shuffleOpenings = 0;       /* [HGM] shuffle */
2193    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2194    switch(newVariant)
2195      {
2196      case VariantShogi:
2197        newWidth = 9;  newHeight = 9;
2198        gameInfo.holdingsSize = 7;
2199      case VariantBughouse:
2200      case VariantCrazyhouse:
2201        newHoldingsWidth = 2; break;
2202      case VariantGreat:
2203        newWidth = 10;
2204      case VariantSuper:
2205        newHoldingsWidth = 2;
2206        gameInfo.holdingsSize = 8;
2207        break;
2208      case VariantGothic:
2209      case VariantCapablanca:
2210      case VariantCapaRandom:
2211        newWidth = 10;
2212      default:
2213        newHoldingsWidth = gameInfo.holdingsSize = 0;
2214      };
2215
2216    if(newWidth  != gameInfo.boardWidth  ||
2217       newHeight != gameInfo.boardHeight ||
2218       newHoldingsWidth != gameInfo.holdingsWidth ) {
2219
2220      /* shift position to new playing area, if needed */
2221      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2222        for(i=0; i<BOARD_HEIGHT; i++)
2223          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2224            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2225              board[i][j];
2226        for(i=0; i<newHeight; i++) {
2227          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2228          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2229        }
2230      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2231        for(i=0; i<BOARD_HEIGHT; i++)
2232          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2233            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2234              board[i][j];
2235      }
2236      gameInfo.boardWidth  = newWidth;
2237      gameInfo.boardHeight = newHeight;
2238      gameInfo.holdingsWidth = newHoldingsWidth;
2239      gameInfo.variant = newVariant;
2240      InitDrawingSizes(-2, 0);
2241    } else gameInfo.variant = newVariant;
2242    CopyBoard(oldBoard, board);   // remember correctly formatted board
2243      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2244    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2245 }
2246
2247 static int loggedOn = FALSE;
2248
2249 /*-- Game start info cache: --*/
2250 int gs_gamenum;
2251 char gs_kind[MSG_SIZ];
2252 static char player1Name[128] = "";
2253 static char player2Name[128] = "";
2254 static char cont_seq[] = "\n\\   ";
2255 static int player1Rating = -1;
2256 static int player2Rating = -1;
2257 /*----------------------------*/
2258
2259 ColorClass curColor = ColorNormal;
2260 int suppressKibitz = 0;
2261
2262 // [HGM] seekgraph
2263 Boolean soughtPending = FALSE;
2264 Boolean seekGraphUp;
2265 #define MAX_SEEK_ADS 200
2266 #define SQUARE 0x80
2267 char *seekAdList[MAX_SEEK_ADS];
2268 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2269 float tcList[MAX_SEEK_ADS];
2270 char colorList[MAX_SEEK_ADS];
2271 int nrOfSeekAds = 0;
2272 int minRating = 1010, maxRating = 2800;
2273 int hMargin = 10, vMargin = 20, h, w;
2274 extern int squareSize, lineGap;
2275
2276 void
2277 PlotSeekAd(int i)
2278 {
2279         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2280         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2281         if(r < minRating+100 && r >=0 ) r = minRating+100;
2282         if(r > maxRating) r = maxRating;
2283         if(tc < 1.) tc = 1.;
2284         if(tc > 95.) tc = 95.;
2285         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2286         y = ((double)r - minRating)/(maxRating - minRating)
2287             * (h-vMargin-squareSize/8-1) + vMargin;
2288         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2289         if(strstr(seekAdList[i], " u ")) color = 1;
2290         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2291            !strstr(seekAdList[i], "bullet") &&
2292            !strstr(seekAdList[i], "blitz") &&
2293            !strstr(seekAdList[i], "standard") ) color = 2;
2294         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2295         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2296 }
2297
2298 void
2299 AddAd(char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2300 {
2301         char buf[MSG_SIZ], *ext = "";
2302         VariantClass v = StringToVariant(type);
2303         if(strstr(type, "wild")) {
2304             ext = type + 4; // append wild number
2305             if(v == VariantFischeRandom) type = "chess960"; else
2306             if(v == VariantLoadable) type = "setup"; else
2307             type = VariantName(v);
2308         }
2309         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2310         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2311             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2312             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2313             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2314             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2315             seekNrList[nrOfSeekAds] = nr;
2316             zList[nrOfSeekAds] = 0;
2317             seekAdList[nrOfSeekAds++] = StrSave(buf);
2318             if(plot) PlotSeekAd(nrOfSeekAds-1);
2319         }
2320 }
2321
2322 void
2323 EraseSeekDot(int i)
2324 {
2325     int x = xList[i], y = yList[i], d=squareSize/4, k;
2326     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2327     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2328     // now replot every dot that overlapped
2329     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2330         int xx = xList[k], yy = yList[k];
2331         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2332             DrawSeekDot(xx, yy, colorList[k]);
2333     }
2334 }
2335
2336 void
2337 RemoveSeekAd(int nr)
2338 {
2339         int i;
2340         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2341             EraseSeekDot(i);
2342             if(seekAdList[i]) free(seekAdList[i]);
2343             seekAdList[i] = seekAdList[--nrOfSeekAds];
2344             seekNrList[i] = seekNrList[nrOfSeekAds];
2345             ratingList[i] = ratingList[nrOfSeekAds];
2346             colorList[i]  = colorList[nrOfSeekAds];
2347             tcList[i] = tcList[nrOfSeekAds];
2348             xList[i]  = xList[nrOfSeekAds];
2349             yList[i]  = yList[nrOfSeekAds];
2350             zList[i]  = zList[nrOfSeekAds];
2351             seekAdList[nrOfSeekAds] = NULL;
2352             break;
2353         }
2354 }
2355
2356 Boolean
2357 MatchSoughtLine(char *line)
2358 {
2359     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2360     int nr, base, inc, u=0; char dummy;
2361
2362     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2363        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2364        (u=1) &&
2365        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2366         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2367         // match: compact and save the line
2368         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2369         return TRUE;
2370     }
2371     return FALSE;
2372 }
2373
2374 int
2375 DrawSeekGraph()
2376 {
2377     int i;
2378     if(!seekGraphUp) return FALSE;
2379     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2380     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2381
2382     DrawSeekBackground(0, 0, w, h);
2383     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2384     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2385     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2386         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2387         yy = h-1-yy;
2388         DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2389         if(i%500 == 0) {
2390             char buf[MSG_SIZ];
2391             snprintf(buf, MSG_SIZ, "%d", i);
2392             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2393         }
2394     }
2395     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2396     for(i=1; i<100; i+=(i<10?1:5)) {
2397         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2398         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2399         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2400             char buf[MSG_SIZ];
2401             snprintf(buf, MSG_SIZ, "%d", i);
2402             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2403         }
2404     }
2405     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2406     return TRUE;
2407 }
2408
2409 int SeekGraphClick(ClickType click, int x, int y, int moving)
2410 {
2411     static int lastDown = 0, displayed = 0, lastSecond;
2412     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2413         if(click == Release || moving) return FALSE;
2414         nrOfSeekAds = 0;
2415         soughtPending = TRUE;
2416         SendToICS(ics_prefix);
2417         SendToICS("sought\n"); // should this be "sought all"?
2418     } else { // issue challenge based on clicked ad
2419         int dist = 10000; int i, closest = 0, second = 0;
2420         for(i=0; i<nrOfSeekAds; i++) {
2421             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2422             if(d < dist) { dist = d; closest = i; }
2423             second += (d - zList[i] < 120); // count in-range ads
2424             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2425         }
2426         if(dist < 120) {
2427             char buf[MSG_SIZ];
2428             second = (second > 1);
2429             if(displayed != closest || second != lastSecond) {
2430                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2431                 lastSecond = second; displayed = closest;
2432             }
2433             if(click == Press) {
2434                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2435                 lastDown = closest;
2436                 return TRUE;
2437             } // on press 'hit', only show info
2438             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2439             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2440             SendToICS(ics_prefix);
2441             SendToICS(buf);
2442             return TRUE; // let incoming board of started game pop down the graph
2443         } else if(click == Release) { // release 'miss' is ignored
2444             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2445             if(moving == 2) { // right up-click
2446                 nrOfSeekAds = 0; // refresh graph
2447                 soughtPending = TRUE;
2448                 SendToICS(ics_prefix);
2449                 SendToICS("sought\n"); // should this be "sought all"?
2450             }
2451             return TRUE;
2452         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2453         // press miss or release hit 'pop down' seek graph
2454         seekGraphUp = FALSE;
2455         DrawPosition(TRUE, NULL);
2456     }
2457     return TRUE;
2458 }
2459
2460 void
2461 read_from_ics(isr, closure, data, count, error)
2462      InputSourceRef isr;
2463      VOIDSTAR closure;
2464      char *data;
2465      int count;
2466      int error;
2467 {
2468 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2469 #define STARTED_NONE 0
2470 #define STARTED_MOVES 1
2471 #define STARTED_BOARD 2
2472 #define STARTED_OBSERVE 3
2473 #define STARTED_HOLDINGS 4
2474 #define STARTED_CHATTER 5
2475 #define STARTED_COMMENT 6
2476 #define STARTED_MOVES_NOHIDE 7
2477
2478     static int started = STARTED_NONE;
2479     static char parse[20000];
2480     static int parse_pos = 0;
2481     static char buf[BUF_SIZE + 1];
2482     static int firstTime = TRUE, intfSet = FALSE;
2483     static ColorClass prevColor = ColorNormal;
2484     static int savingComment = FALSE;
2485     static int cmatch = 0; // continuation sequence match
2486     char *bp;
2487     char str[MSG_SIZ];
2488     int i, oldi;
2489     int buf_len;
2490     int next_out;
2491     int tkind;
2492     int backup;    /* [DM] For zippy color lines */
2493     char *p;
2494     char talker[MSG_SIZ]; // [HGM] chat
2495     int channel;
2496
2497     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2498
2499     if (appData.debugMode) {
2500       if (!error) {
2501         fprintf(debugFP, "<ICS: ");
2502         show_bytes(debugFP, data, count);
2503         fprintf(debugFP, "\n");
2504       }
2505     }
2506
2507     if (appData.debugMode) { int f = forwardMostMove;
2508         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2509                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2510                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2511     }
2512     if (count > 0) {
2513         /* If last read ended with a partial line that we couldn't parse,
2514            prepend it to the new read and try again. */
2515         if (leftover_len > 0) {
2516             for (i=0; i<leftover_len; i++)
2517               buf[i] = buf[leftover_start + i];
2518         }
2519
2520     /* copy new characters into the buffer */
2521     bp = buf + leftover_len;
2522     buf_len=leftover_len;
2523     for (i=0; i<count; i++)
2524     {
2525         // ignore these
2526         if (data[i] == '\r')
2527             continue;
2528
2529         // join lines split by ICS?
2530         if (!appData.noJoin)
2531         {
2532             /*
2533                 Joining just consists of finding matches against the
2534                 continuation sequence, and discarding that sequence
2535                 if found instead of copying it.  So, until a match
2536                 fails, there's nothing to do since it might be the
2537                 complete sequence, and thus, something we don't want
2538                 copied.
2539             */
2540             if (data[i] == cont_seq[cmatch])
2541             {
2542                 cmatch++;
2543                 if (cmatch == strlen(cont_seq))
2544                 {
2545                     cmatch = 0; // complete match.  just reset the counter
2546
2547                     /*
2548                         it's possible for the ICS to not include the space
2549                         at the end of the last word, making our [correct]
2550                         join operation fuse two separate words.  the server
2551                         does this when the space occurs at the width setting.
2552                     */
2553                     if (!buf_len || buf[buf_len-1] != ' ')
2554                     {
2555                         *bp++ = ' ';
2556                         buf_len++;
2557                     }
2558                 }
2559                 continue;
2560             }
2561             else if (cmatch)
2562             {
2563                 /*
2564                     match failed, so we have to copy what matched before
2565                     falling through and copying this character.  In reality,
2566                     this will only ever be just the newline character, but
2567                     it doesn't hurt to be precise.
2568                 */
2569                 strncpy(bp, cont_seq, cmatch);
2570                 bp += cmatch;
2571                 buf_len += cmatch;
2572                 cmatch = 0;
2573             }
2574         }
2575
2576         // copy this char
2577         *bp++ = data[i];
2578         buf_len++;
2579     }
2580
2581         buf[buf_len] = NULLCHAR;
2582 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2583         next_out = 0;
2584         leftover_start = 0;
2585
2586         i = 0;
2587         while (i < buf_len) {
2588             /* Deal with part of the TELNET option negotiation
2589                protocol.  We refuse to do anything beyond the
2590                defaults, except that we allow the WILL ECHO option,
2591                which ICS uses to turn off password echoing when we are
2592                directly connected to it.  We reject this option
2593                if localLineEditing mode is on (always on in xboard)
2594                and we are talking to port 23, which might be a real
2595                telnet server that will try to keep WILL ECHO on permanently.
2596              */
2597             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2598                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2599                 unsigned char option;
2600                 oldi = i;
2601                 switch ((unsigned char) buf[++i]) {
2602                   case TN_WILL:
2603                     if (appData.debugMode)
2604                       fprintf(debugFP, "\n<WILL ");
2605                     switch (option = (unsigned char) buf[++i]) {
2606                       case TN_ECHO:
2607                         if (appData.debugMode)
2608                           fprintf(debugFP, "ECHO ");
2609                         /* Reply only if this is a change, according
2610                            to the protocol rules. */
2611                         if (remoteEchoOption) break;
2612                         if (appData.localLineEditing &&
2613                             atoi(appData.icsPort) == TN_PORT) {
2614                             TelnetRequest(TN_DONT, TN_ECHO);
2615                         } else {
2616                             EchoOff();
2617                             TelnetRequest(TN_DO, TN_ECHO);
2618                             remoteEchoOption = TRUE;
2619                         }
2620                         break;
2621                       default:
2622                         if (appData.debugMode)
2623                           fprintf(debugFP, "%d ", option);
2624                         /* Whatever this is, we don't want it. */
2625                         TelnetRequest(TN_DONT, option);
2626                         break;
2627                     }
2628                     break;
2629                   case TN_WONT:
2630                     if (appData.debugMode)
2631                       fprintf(debugFP, "\n<WONT ");
2632                     switch (option = (unsigned char) buf[++i]) {
2633                       case TN_ECHO:
2634                         if (appData.debugMode)
2635                           fprintf(debugFP, "ECHO ");
2636                         /* Reply only if this is a change, according
2637                            to the protocol rules. */
2638                         if (!remoteEchoOption) break;
2639                         EchoOn();
2640                         TelnetRequest(TN_DONT, TN_ECHO);
2641                         remoteEchoOption = FALSE;
2642                         break;
2643                       default:
2644                         if (appData.debugMode)
2645                           fprintf(debugFP, "%d ", (unsigned char) option);
2646                         /* Whatever this is, it must already be turned
2647                            off, because we never agree to turn on
2648                            anything non-default, so according to the
2649                            protocol rules, we don't reply. */
2650                         break;
2651                     }
2652                     break;
2653                   case TN_DO:
2654                     if (appData.debugMode)
2655                       fprintf(debugFP, "\n<DO ");
2656                     switch (option = (unsigned char) buf[++i]) {
2657                       default:
2658                         /* Whatever this is, we refuse to do it. */
2659                         if (appData.debugMode)
2660                           fprintf(debugFP, "%d ", option);
2661                         TelnetRequest(TN_WONT, option);
2662                         break;
2663                     }
2664                     break;
2665                   case TN_DONT:
2666                     if (appData.debugMode)
2667                       fprintf(debugFP, "\n<DONT ");
2668                     switch (option = (unsigned char) buf[++i]) {
2669                       default:
2670                         if (appData.debugMode)
2671                           fprintf(debugFP, "%d ", option);
2672                         /* Whatever this is, we are already not doing
2673                            it, because we never agree to do anything
2674                            non-default, so according to the protocol
2675                            rules, we don't reply. */
2676                         break;
2677                     }
2678                     break;
2679                   case TN_IAC:
2680                     if (appData.debugMode)
2681                       fprintf(debugFP, "\n<IAC ");
2682                     /* Doubled IAC; pass it through */
2683                     i--;
2684                     break;
2685                   default:
2686                     if (appData.debugMode)
2687                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2688                     /* Drop all other telnet commands on the floor */
2689                     break;
2690                 }
2691                 if (oldi > next_out)
2692                   SendToPlayer(&buf[next_out], oldi - next_out);
2693                 if (++i > next_out)
2694                   next_out = i;
2695                 continue;
2696             }
2697
2698             /* OK, this at least will *usually* work */
2699             if (!loggedOn && looking_at(buf, &i, "ics%")) {
2700                 loggedOn = TRUE;
2701             }
2702
2703             if (loggedOn && !intfSet) {
2704                 if (ics_type == ICS_ICC) {
2705                   snprintf(str, MSG_SIZ,
2706                           "/set-quietly interface %s\n/set-quietly style 12\n",
2707                           programVersion);
2708                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2709                       strcat(str, "/set-2 51 1\n/set seek 1\n");
2710                 } else if (ics_type == ICS_CHESSNET) {
2711                   snprintf(str, MSG_SIZ, "/style 12\n");
2712                 } else {
2713                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2714                   strcat(str, programVersion);
2715                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2716                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2717                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
2718 #ifdef WIN32
2719                   strcat(str, "$iset nohighlight 1\n");
2720 #endif
2721                   strcat(str, "$iset lock 1\n$style 12\n");
2722                 }
2723                 SendToICS(str);
2724                 NotifyFrontendLogin();
2725                 intfSet = TRUE;
2726             }
2727
2728             if (started == STARTED_COMMENT) {
2729                 /* Accumulate characters in comment */
2730                 parse[parse_pos++] = buf[i];
2731                 if (buf[i] == '\n') {
2732                     parse[parse_pos] = NULLCHAR;
2733                     if(chattingPartner>=0) {
2734                         char mess[MSG_SIZ];
2735                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2736                         OutputChatMessage(chattingPartner, mess);
2737                         chattingPartner = -1;
2738                         next_out = i+1; // [HGM] suppress printing in ICS window
2739                     } else
2740                     if(!suppressKibitz) // [HGM] kibitz
2741                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2742                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2743                         int nrDigit = 0, nrAlph = 0, j;
2744                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2745                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2746                         parse[parse_pos] = NULLCHAR;
2747                         // try to be smart: if it does not look like search info, it should go to
2748                         // ICS interaction window after all, not to engine-output window.
2749                         for(j=0; j<parse_pos; j++) { // count letters and digits
2750                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2751                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
2752                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
2753                         }
2754                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2755                             int depth=0; float score;
2756                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2757                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2758                                 pvInfoList[forwardMostMove-1].depth = depth;
2759                                 pvInfoList[forwardMostMove-1].score = 100*score;
2760                             }
2761                             OutputKibitz(suppressKibitz, parse);
2762                         } else {
2763                             char tmp[MSG_SIZ];
2764                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2765                             SendToPlayer(tmp, strlen(tmp));
2766                         }
2767                         next_out = i+1; // [HGM] suppress printing in ICS window
2768                     }
2769                     started = STARTED_NONE;
2770                 } else {
2771                     /* Don't match patterns against characters in comment */
2772                     i++;
2773                     continue;
2774                 }
2775             }
2776             if (started == STARTED_CHATTER) {
2777                 if (buf[i] != '\n') {
2778                     /* Don't match patterns against characters in chatter */
2779                     i++;
2780                     continue;
2781                 }
2782                 started = STARTED_NONE;
2783                 if(suppressKibitz) next_out = i+1;
2784             }
2785
2786             /* Kludge to deal with rcmd protocol */
2787             if (firstTime && looking_at(buf, &i, "\001*")) {
2788                 DisplayFatalError(&buf[1], 0, 1);
2789                 continue;
2790             } else {
2791                 firstTime = FALSE;
2792             }
2793
2794             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2795                 ics_type = ICS_ICC;
2796                 ics_prefix = "/";
2797                 if (appData.debugMode)
2798                   fprintf(debugFP, "ics_type %d\n", ics_type);
2799                 continue;
2800             }
2801             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2802                 ics_type = ICS_FICS;
2803                 ics_prefix = "$";
2804                 if (appData.debugMode)
2805                   fprintf(debugFP, "ics_type %d\n", ics_type);
2806                 continue;
2807             }
2808             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2809                 ics_type = ICS_CHESSNET;
2810                 ics_prefix = "/";
2811                 if (appData.debugMode)
2812                   fprintf(debugFP, "ics_type %d\n", ics_type);
2813                 continue;
2814             }
2815
2816             if (!loggedOn &&
2817                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2818                  looking_at(buf, &i, "Logging you in as \"*\"") ||
2819                  looking_at(buf, &i, "will be \"*\""))) {
2820               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2821               continue;
2822             }
2823
2824             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2825               char buf[MSG_SIZ];
2826               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2827               DisplayIcsInteractionTitle(buf);
2828               have_set_title = TRUE;
2829             }
2830
2831             /* skip finger notes */
2832             if (started == STARTED_NONE &&
2833                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2834                  (buf[i] == '1' && buf[i+1] == '0')) &&
2835                 buf[i+2] == ':' && buf[i+3] == ' ') {
2836               started = STARTED_CHATTER;
2837               i += 3;
2838               continue;
2839             }
2840
2841             oldi = i;
2842             // [HGM] seekgraph: recognize sought lines and end-of-sought message
2843             if(appData.seekGraph) {
2844                 if(soughtPending && MatchSoughtLine(buf+i)) {
2845                     i = strstr(buf+i, "rated") - buf;
2846                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2847                     next_out = leftover_start = i;
2848                     started = STARTED_CHATTER;
2849                     suppressKibitz = TRUE;
2850                     continue;
2851                 }
2852                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2853                         && looking_at(buf, &i, "* ads displayed")) {
2854                     soughtPending = FALSE;
2855                     seekGraphUp = TRUE;
2856                     DrawSeekGraph();
2857                     continue;
2858                 }
2859                 if(appData.autoRefresh) {
2860                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2861                         int s = (ics_type == ICS_ICC); // ICC format differs
2862                         if(seekGraphUp)
2863                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2864                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2865                         looking_at(buf, &i, "*% "); // eat prompt
2866                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2867                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2868                         next_out = i; // suppress
2869                         continue;
2870                     }
2871                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2872                         char *p = star_match[0];
2873                         while(*p) {
2874                             if(seekGraphUp) RemoveSeekAd(atoi(p));
2875                             while(*p && *p++ != ' '); // next
2876                         }
2877                         looking_at(buf, &i, "*% "); // eat prompt
2878                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2879                         next_out = i;
2880                         continue;
2881                     }
2882                 }
2883             }
2884
2885             /* skip formula vars */
2886             if (started == STARTED_NONE &&
2887                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2888               started = STARTED_CHATTER;
2889               i += 3;
2890               continue;
2891             }
2892
2893             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2894             if (appData.autoKibitz && started == STARTED_NONE &&
2895                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
2896                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2897                 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2898                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2899                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
2900                         suppressKibitz = TRUE;
2901                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2902                         next_out = i;
2903                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2904                                 && (gameMode == IcsPlayingWhite)) ||
2905                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
2906                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
2907                             started = STARTED_CHATTER; // own kibitz we simply discard
2908                         else {
2909                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
2910                             parse_pos = 0; parse[0] = NULLCHAR;
2911                             savingComment = TRUE;
2912                             suppressKibitz = gameMode != IcsObserving ? 2 :
2913                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2914                         }
2915                         continue;
2916                 } else
2917                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2918                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2919                          && atoi(star_match[0])) {
2920                     // suppress the acknowledgements of our own autoKibitz
2921                     char *p;
2922                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2923                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2924                     SendToPlayer(star_match[0], strlen(star_match[0]));
2925                     if(looking_at(buf, &i, "*% ")) // eat prompt
2926                         suppressKibitz = FALSE;
2927                     next_out = i;
2928                     continue;
2929                 }
2930             } // [HGM] kibitz: end of patch
2931
2932             // [HGM] chat: intercept tells by users for which we have an open chat window
2933             channel = -1;
2934             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2935                                            looking_at(buf, &i, "* whispers:") ||
2936                                            looking_at(buf, &i, "* kibitzes:") ||
2937                                            looking_at(buf, &i, "* shouts:") ||
2938                                            looking_at(buf, &i, "* c-shouts:") ||
2939                                            looking_at(buf, &i, "--> * ") ||
2940                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2941                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2942                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2943                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2944                 int p;
2945                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2946                 chattingPartner = -1;
2947
2948                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2949                 for(p=0; p<MAX_CHAT; p++) {
2950                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2951                     talker[0] = '['; strcat(talker, "] ");
2952                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2953                     chattingPartner = p; break;
2954                     }
2955                 } else
2956                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2957                 for(p=0; p<MAX_CHAT; p++) {
2958                     if(!strcmp("kibitzes", chatPartner[p])) {
2959                         talker[0] = '['; strcat(talker, "] ");
2960                         chattingPartner = p; break;
2961                     }
2962                 } else
2963                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2964                 for(p=0; p<MAX_CHAT; p++) {
2965                     if(!strcmp("whispers", chatPartner[p])) {
2966                         talker[0] = '['; strcat(talker, "] ");
2967                         chattingPartner = p; break;
2968                     }
2969                 } else
2970                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2971                   if(buf[i-8] == '-' && buf[i-3] == 't')
2972                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2973                     if(!strcmp("c-shouts", chatPartner[p])) {
2974                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2975                         chattingPartner = p; break;
2976                     }
2977                   }
2978                   if(chattingPartner < 0)
2979                   for(p=0; p<MAX_CHAT; p++) {
2980                     if(!strcmp("shouts", chatPartner[p])) {
2981                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2982                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2983                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2984                         chattingPartner = p; break;
2985                     }
2986                   }
2987                 }
2988                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2989                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2990                     talker[0] = 0; Colorize(ColorTell, FALSE);
2991                     chattingPartner = p; break;
2992                 }
2993                 if(chattingPartner<0) i = oldi; else {
2994                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2995                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2996                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2997                     started = STARTED_COMMENT;
2998                     parse_pos = 0; parse[0] = NULLCHAR;
2999                     savingComment = 3 + chattingPartner; // counts as TRUE
3000                     suppressKibitz = TRUE;
3001                     continue;
3002                 }
3003             } // [HGM] chat: end of patch
3004
3005           backup = i;
3006             if (appData.zippyTalk || appData.zippyPlay) {
3007                 /* [DM] Backup address for color zippy lines */
3008 #if ZIPPY
3009                if (loggedOn == TRUE)
3010                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3011                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3012 #endif
3013             } // [DM] 'else { ' deleted
3014                 if (
3015                     /* Regular tells and says */
3016                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3017                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3018                     looking_at(buf, &i, "* says: ") ||
3019                     /* Don't color "message" or "messages" output */
3020                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3021                     looking_at(buf, &i, "*. * at *:*: ") ||
3022                     looking_at(buf, &i, "--* (*:*): ") ||
3023                     /* Message notifications (same color as tells) */
3024                     looking_at(buf, &i, "* has left a message ") ||
3025                     looking_at(buf, &i, "* just sent you a message:\n") ||
3026                     /* Whispers and kibitzes */
3027                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3028                     looking_at(buf, &i, "* kibitzes: ") ||
3029                     /* Channel tells */
3030                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3031
3032                   if (tkind == 1 && strchr(star_match[0], ':')) {
3033                       /* Avoid "tells you:" spoofs in channels */
3034                      tkind = 3;
3035                   }
3036                   if (star_match[0][0] == NULLCHAR ||
3037                       strchr(star_match[0], ' ') ||
3038                       (tkind == 3 && strchr(star_match[1], ' '))) {
3039                     /* Reject bogus matches */
3040                     i = oldi;
3041                   } else {
3042                     if (appData.colorize) {
3043                       if (oldi > next_out) {
3044                         SendToPlayer(&buf[next_out], oldi - next_out);
3045                         next_out = oldi;
3046                       }
3047                       switch (tkind) {
3048                       case 1:
3049                         Colorize(ColorTell, FALSE);
3050                         curColor = ColorTell;
3051                         break;
3052                       case 2:
3053                         Colorize(ColorKibitz, FALSE);
3054                         curColor = ColorKibitz;
3055                         break;
3056                       case 3:
3057                         p = strrchr(star_match[1], '(');
3058                         if (p == NULL) {
3059                           p = star_match[1];
3060                         } else {
3061                           p++;
3062                         }
3063                         if (atoi(p) == 1) {
3064                           Colorize(ColorChannel1, FALSE);
3065                           curColor = ColorChannel1;
3066                         } else {
3067                           Colorize(ColorChannel, FALSE);
3068                           curColor = ColorChannel;
3069                         }
3070                         break;
3071                       case 5:
3072                         curColor = ColorNormal;
3073                         break;
3074                       }
3075                     }
3076                     if (started == STARTED_NONE && appData.autoComment &&
3077                         (gameMode == IcsObserving ||
3078                          gameMode == IcsPlayingWhite ||
3079                          gameMode == IcsPlayingBlack)) {
3080                       parse_pos = i - oldi;
3081                       memcpy(parse, &buf[oldi], parse_pos);
3082                       parse[parse_pos] = NULLCHAR;
3083                       started = STARTED_COMMENT;
3084                       savingComment = TRUE;
3085                     } else {
3086                       started = STARTED_CHATTER;
3087                       savingComment = FALSE;
3088                     }
3089                     loggedOn = TRUE;
3090                     continue;
3091                   }
3092                 }
3093
3094                 if (looking_at(buf, &i, "* s-shouts: ") ||
3095                     looking_at(buf, &i, "* c-shouts: ")) {
3096                     if (appData.colorize) {
3097                         if (oldi > next_out) {
3098                             SendToPlayer(&buf[next_out], oldi - next_out);
3099                             next_out = oldi;
3100                         }
3101                         Colorize(ColorSShout, FALSE);
3102                         curColor = ColorSShout;
3103                     }
3104                     loggedOn = TRUE;
3105                     started = STARTED_CHATTER;
3106                     continue;
3107                 }
3108
3109                 if (looking_at(buf, &i, "--->")) {
3110                     loggedOn = TRUE;
3111                     continue;
3112                 }
3113
3114                 if (looking_at(buf, &i, "* shouts: ") ||
3115                     looking_at(buf, &i, "--> ")) {
3116                     if (appData.colorize) {
3117                         if (oldi > next_out) {
3118                             SendToPlayer(&buf[next_out], oldi - next_out);
3119                             next_out = oldi;
3120                         }
3121                         Colorize(ColorShout, FALSE);
3122                         curColor = ColorShout;
3123                     }
3124                     loggedOn = TRUE;
3125                     started = STARTED_CHATTER;
3126                     continue;
3127                 }
3128
3129                 if (looking_at( buf, &i, "Challenge:")) {
3130                     if (appData.colorize) {
3131                         if (oldi > next_out) {
3132                             SendToPlayer(&buf[next_out], oldi - next_out);
3133                             next_out = oldi;
3134                         }
3135                         Colorize(ColorChallenge, FALSE);
3136                         curColor = ColorChallenge;
3137                     }
3138                     loggedOn = TRUE;
3139                     continue;
3140                 }
3141
3142                 if (looking_at(buf, &i, "* offers you") ||
3143                     looking_at(buf, &i, "* offers to be") ||
3144                     looking_at(buf, &i, "* would like to") ||
3145                     looking_at(buf, &i, "* requests to") ||
3146                     looking_at(buf, &i, "Your opponent offers") ||
3147                     looking_at(buf, &i, "Your opponent requests")) {
3148
3149                     if (appData.colorize) {
3150                         if (oldi > next_out) {
3151                             SendToPlayer(&buf[next_out], oldi - next_out);
3152                             next_out = oldi;
3153                         }
3154                         Colorize(ColorRequest, FALSE);
3155                         curColor = ColorRequest;
3156                     }
3157                     continue;
3158                 }
3159
3160                 if (looking_at(buf, &i, "* (*) seeking")) {
3161                     if (appData.colorize) {
3162                         if (oldi > next_out) {
3163                             SendToPlayer(&buf[next_out], oldi - next_out);
3164                             next_out = oldi;
3165                         }
3166                         Colorize(ColorSeek, FALSE);
3167                         curColor = ColorSeek;
3168                     }
3169                     continue;
3170             }
3171
3172           if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3173
3174             if (looking_at(buf, &i, "\\   ")) {
3175                 if (prevColor != ColorNormal) {
3176                     if (oldi > next_out) {
3177                         SendToPlayer(&buf[next_out], oldi - next_out);
3178                         next_out = oldi;
3179                     }
3180                     Colorize(prevColor, TRUE);
3181                     curColor = prevColor;
3182                 }
3183                 if (savingComment) {
3184                     parse_pos = i - oldi;
3185                     memcpy(parse, &buf[oldi], parse_pos);
3186                     parse[parse_pos] = NULLCHAR;
3187                     started = STARTED_COMMENT;
3188                     if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3189                         chattingPartner = savingComment - 3; // kludge to remember the box
3190                 } else {
3191                     started = STARTED_CHATTER;
3192                 }
3193                 continue;
3194             }
3195
3196             if (looking_at(buf, &i, "Black Strength :") ||
3197                 looking_at(buf, &i, "<<< style 10 board >>>") ||
3198                 looking_at(buf, &i, "<10>") ||
3199                 looking_at(buf, &i, "#@#")) {
3200                 /* Wrong board style */
3201                 loggedOn = TRUE;
3202                 SendToICS(ics_prefix);
3203                 SendToICS("set style 12\n");
3204                 SendToICS(ics_prefix);
3205                 SendToICS("refresh\n");
3206                 continue;
3207             }
3208
3209             if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3210                 ICSInitScript();
3211                 have_sent_ICS_logon = 1;
3212                 continue;
3213             }
3214
3215             if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3216                 (looking_at(buf, &i, "\n<12> ") ||
3217                  looking_at(buf, &i, "<12> "))) {
3218                 loggedOn = TRUE;
3219                 if (oldi > next_out) {
3220                     SendToPlayer(&buf[next_out], oldi - next_out);
3221                 }
3222                 next_out = i;
3223                 started = STARTED_BOARD;
3224                 parse_pos = 0;
3225                 continue;
3226             }
3227
3228             if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3229                 looking_at(buf, &i, "<b1> ")) {
3230                 if (oldi > next_out) {
3231                     SendToPlayer(&buf[next_out], oldi - next_out);
3232                 }
3233                 next_out = i;
3234                 started = STARTED_HOLDINGS;
3235                 parse_pos = 0;
3236                 continue;
3237             }
3238
3239             if (looking_at(buf, &i, "* *vs. * *--- *")) {
3240                 loggedOn = TRUE;
3241                 /* Header for a move list -- first line */
3242
3243                 switch (ics_getting_history) {
3244                   case H_FALSE:
3245                     switch (gameMode) {
3246                       case IcsIdle:
3247                       case BeginningOfGame:
3248                         /* User typed "moves" or "oldmoves" while we
3249                            were idle.  Pretend we asked for these
3250                            moves and soak them up so user can step
3251                            through them and/or save them.
3252                            */
3253                         Reset(FALSE, TRUE);
3254                         gameMode = IcsObserving;
3255                         ModeHighlight();
3256                         ics_gamenum = -1;
3257                         ics_getting_history = H_GOT_UNREQ_HEADER;
3258                         break;
3259                       case EditGame: /*?*/
3260                       case EditPosition: /*?*/
3261                         /* Should above feature work in these modes too? */
3262                         /* For now it doesn't */
3263                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3264                         break;
3265                       default:
3266                         ics_getting_history = H_GOT_UNWANTED_HEADER;
3267                         break;
3268                     }
3269                     break;
3270                   case H_REQUESTED:
3271                     /* Is this the right one? */
3272                     if (gameInfo.white && gameInfo.black &&
3273                         strcmp(gameInfo.white, star_match[0]) == 0 &&
3274                         strcmp(gameInfo.black, star_match[2]) == 0) {
3275                         /* All is well */
3276                         ics_getting_history = H_GOT_REQ_HEADER;
3277                     }
3278                     break;
3279                   case H_GOT_REQ_HEADER:
3280                   case H_GOT_UNREQ_HEADER:
3281                   case H_GOT_UNWANTED_HEADER:
3282                   case H_GETTING_MOVES:
3283                     /* Should not happen */
3284                     DisplayError(_("Error gathering move list: two headers"), 0);
3285                     ics_getting_history = H_FALSE;
3286                     break;
3287                 }
3288
3289                 /* Save player ratings into gameInfo if needed */
3290                 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3291                      ics_getting_history == H_GOT_UNREQ_HEADER) &&
3292                     (gameInfo.whiteRating == -1 ||
3293                      gameInfo.blackRating == -1)) {
3294
3295                     gameInfo.whiteRating = string_to_rating(star_match[1]);
3296                     gameInfo.blackRating = string_to_rating(star_match[3]);
3297                     if (appData.debugMode)
3298                       fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3299                               gameInfo.whiteRating, gameInfo.blackRating);
3300                 }
3301                 continue;
3302             }
3303
3304             if (looking_at(buf, &i,
3305               "* * match, initial time: * minute*, increment: * second")) {
3306                 /* Header for a move list -- second line */
3307                 /* Initial board will follow if this is a wild game */
3308                 if (gameInfo.event != NULL) free(gameInfo.event);
3309                 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3310                 gameInfo.event = StrSave(str);
3311                 /* [HGM] we switched variant. Translate boards if needed. */
3312                 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3313                 continue;
3314             }
3315
3316             if (looking_at(buf, &i, "Move  ")) {
3317                 /* Beginning of a move list */
3318                 switch (ics_getting_history) {
3319                   case H_FALSE:
3320                     /* Normally should not happen */
3321                     /* Maybe user hit reset while we were parsing */
3322                     break;
3323                   case H_REQUESTED:
3324                     /* Happens if we are ignoring a move list that is not
3325                      * the one we just requested.  Common if the user
3326                      * tries to observe two games without turning off
3327                      * getMoveList */
3328                     break;
3329                   case H_GETTING_MOVES:
3330                     /* Should not happen */
3331                     DisplayError(_("Error gathering move list: nested"), 0);
3332                     ics_getting_history = H_FALSE;
3333                     break;
3334                   case H_GOT_REQ_HEADER:
3335                     ics_getting_history = H_GETTING_MOVES;
3336                     started = STARTED_MOVES;
3337                     parse_pos = 0;
3338                     if (oldi > next_out) {
3339                         SendToPlayer(&buf[next_out], oldi - next_out);
3340                     }
3341                     break;
3342                   case H_GOT_UNREQ_HEADER:
3343                     ics_getting_history = H_GETTING_MOVES;
3344                     started = STARTED_MOVES_NOHIDE;
3345                     parse_pos = 0;
3346                     break;
3347                   case H_GOT_UNWANTED_HEADER:
3348                     ics_getting_history = H_FALSE;
3349                     break;
3350                 }
3351                 continue;
3352             }
3353
3354             if (looking_at(buf, &i, "% ") ||
3355                 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3356                  && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3357                 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3358                     soughtPending = FALSE;
3359                     seekGraphUp = TRUE;
3360                     DrawSeekGraph();
3361                 }
3362                 if(suppressKibitz) next_out = i;
3363                 savingComment = FALSE;
3364                 suppressKibitz = 0;
3365                 switch (started) {
3366                   case STARTED_MOVES:
3367                   case STARTED_MOVES_NOHIDE:
3368                     memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3369                     parse[parse_pos + i - oldi] = NULLCHAR;
3370                     ParseGameHistory(parse);
3371 #if ZIPPY
3372                     if (appData.zippyPlay && first.initDone) {
3373                         FeedMovesToProgram(&first, forwardMostMove);
3374                         if (gameMode == IcsPlayingWhite) {
3375                             if (WhiteOnMove(forwardMostMove)) {
3376                                 if (first.sendTime) {
3377                                   if (first.useColors) {
3378                                     SendToProgram("black\n", &first);
3379                                   }
3380                                   SendTimeRemaining(&first, TRUE);
3381                                 }
3382                                 if (first.useColors) {
3383                                   SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3384                                 }
3385                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3386                                 first.maybeThinking = TRUE;
3387                             } else {
3388                                 if (first.usePlayother) {
3389                                   if (first.sendTime) {
3390                                     SendTimeRemaining(&first, TRUE);
3391                                   }
3392                                   SendToProgram("playother\n", &first);
3393                                   firstMove = FALSE;
3394                                 } else {
3395                                   firstMove = TRUE;
3396                                 }
3397                             }
3398                         } else if (gameMode == IcsPlayingBlack) {
3399                             if (!WhiteOnMove(forwardMostMove)) {
3400                                 if (first.sendTime) {
3401                                   if (first.useColors) {
3402                                     SendToProgram("white\n", &first);
3403                                   }
3404                                   SendTimeRemaining(&first, FALSE);
3405                                 }
3406                                 if (first.useColors) {
3407                                   SendToProgram("black\n", &first);
3408                                 }
3409                                 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3410                                 first.maybeThinking = TRUE;
3411                             } else {
3412                                 if (first.usePlayother) {
3413                                   if (first.sendTime) {
3414                                     SendTimeRemaining(&first, FALSE);
3415                                   }
3416                                   SendToProgram("playother\n", &first);
3417                                   firstMove = FALSE;
3418                                 } else {
3419                                   firstMove = TRUE;
3420                                 }
3421                             }
3422                         }
3423                     }
3424 #endif
3425                     if (gameMode == IcsObserving && ics_gamenum == -1) {
3426                         /* Moves came from oldmoves or moves command
3427                            while we weren't doing anything else.
3428                            */
3429                         currentMove = forwardMostMove;
3430                         ClearHighlights();/*!!could figure this out*/
3431                         flipView = appData.flipView;
3432                         DrawPosition(TRUE, boards[currentMove]);
3433                         DisplayBothClocks();
3434                         snprintf(str, MSG_SIZ, "%s vs. %s",
3435                                 gameInfo.white, gameInfo.black);
3436                         DisplayTitle(str);
3437                         gameMode = IcsIdle;
3438                     } else {
3439                         /* Moves were history of an active game */
3440                         if (gameInfo.resultDetails != NULL) {
3441                             free(gameInfo.resultDetails);
3442                             gameInfo.resultDetails = NULL;
3443                         }
3444                     }
3445                     HistorySet(parseList, backwardMostMove,
3446                                forwardMostMove, currentMove-1);
3447                     DisplayMove(currentMove - 1);
3448                     if (started == STARTED_MOVES) next_out = i;
3449                     started = STARTED_NONE;
3450                     ics_getting_history = H_FALSE;
3451                     break;
3452
3453                   case STARTED_OBSERVE:
3454                     started = STARTED_NONE;
3455                     SendToICS(ics_prefix);
3456                     SendToICS("refresh\n");
3457                     break;
3458
3459                   default:
3460                     break;
3461                 }
3462                 if(bookHit) { // [HGM] book: simulate book reply
3463                     static char bookMove[MSG_SIZ]; // a bit generous?
3464
3465                     programStats.nodes = programStats.depth = programStats.time =
3466                     programStats.score = programStats.got_only_move = 0;
3467                     sprintf(programStats.movelist, "%s (xbook)", bookHit);
3468
3469                     safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3470                     strcat(bookMove, bookHit);
3471                     HandleMachineMove(bookMove, &first);
3472                 }
3473                 continue;
3474             }
3475
3476             if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3477                  started == STARTED_HOLDINGS ||
3478                  started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3479                 /* Accumulate characters in move list or board */
3480                 parse[parse_pos++] = buf[i];
3481             }
3482
3483             /* Start of game messages.  Mostly we detect start of game
3484                when the first board image arrives.  On some versions
3485                of the ICS, though, we need to do a "refresh" after starting
3486                to observe in order to get the current board right away. */
3487             if (looking_at(buf, &i, "Adding game * to observation list")) {
3488                 started = STARTED_OBSERVE;
3489                 continue;
3490             }
3491
3492             /* Handle auto-observe */
3493             if (appData.autoObserve &&
3494                 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3495                 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3496                 char *player;
3497                 /* Choose the player that was highlighted, if any. */
3498                 if (star_match[0][0] == '\033' ||
3499                     star_match[1][0] != '\033') {
3500                     player = star_match[0];
3501                 } else {
3502                     player = star_match[2];
3503                 }
3504                 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3505                         ics_prefix, StripHighlightAndTitle(player));
3506                 SendToICS(str);
3507
3508                 /* Save ratings from notify string */
3509                 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3510                 player1Rating = string_to_rating(star_match[1]);
3511                 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3512                 player2Rating = string_to_rating(star_match[3]);
3513
3514                 if (appData.debugMode)
3515                   fprintf(debugFP,
3516                           "Ratings from 'Game notification:' %s %d, %s %d\n",
3517                           player1Name, player1Rating,
3518                           player2Name, player2Rating);
3519
3520                 continue;
3521             }
3522
3523             /* Deal with automatic examine mode after a game,
3524                and with IcsObserving -> IcsExamining transition */
3525             if (looking_at(buf, &i, "Entering examine mode for game *") ||
3526                 looking_at(buf, &i, "has made you an examiner of game *")) {
3527
3528                 int gamenum = atoi(star_match[0]);
3529                 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3530                     gamenum == ics_gamenum) {
3531                     /* We were already playing or observing this game;
3532                        no need to refetch history */
3533                     gameMode = IcsExamining;
3534                     if (pausing) {
3535                         pauseExamForwardMostMove = forwardMostMove;
3536                     } else if (currentMove < forwardMostMove) {
3537                         ForwardInner(forwardMostMove);
3538                     }
3539                 } else {
3540                     /* I don't think this case really can happen */
3541                     SendToICS(ics_prefix);
3542                     SendToICS("refresh\n");
3543                 }
3544                 continue;
3545             }
3546
3547             /* Error messages */
3548 //          if (ics_user_moved) {
3549             if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3550                 if (looking_at(buf, &i, "Illegal move") ||
3551                     looking_at(buf, &i, "Not a legal move") ||
3552                     looking_at(buf, &i, "Your king is in check") ||
3553                     looking_at(buf, &i, "It isn't your turn") ||
3554                     looking_at(buf, &i, "It is not your move")) {
3555                     /* Illegal move */
3556                     if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3557                         currentMove = forwardMostMove-1;
3558                         DisplayMove(currentMove - 1); /* before DMError */
3559                         DrawPosition(FALSE, boards[currentMove]);
3560                         SwitchClocks(forwardMostMove-1); // [HGM] race
3561                         DisplayBothClocks();
3562                     }
3563                     DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3564                     ics_user_moved = 0;
3565                     continue;
3566                 }
3567             }
3568
3569             if (looking_at(buf, &i, "still have time") ||
3570                 looking_at(buf, &i, "not out of time") ||
3571                 looking_at(buf, &i, "either player is out of time") ||
3572                 looking_at(buf, &i, "has timeseal; checking")) {
3573                 /* We must have called his flag a little too soon */
3574                 whiteFlag = blackFlag = FALSE;
3575                 continue;
3576             }
3577
3578             if (looking_at(buf, &i, "added * seconds to") ||
3579                 looking_at(buf, &i, "seconds were added to")) {
3580                 /* Update the clocks */
3581                 SendToICS(ics_prefix);
3582                 SendToICS("refresh\n");
3583                 continue;
3584             }
3585
3586             if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3587                 ics_clock_paused = TRUE;
3588                 StopClocks();
3589                 continue;
3590             }
3591
3592             if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3593                 ics_clock_paused = FALSE;
3594                 StartClocks();
3595                 continue;
3596             }
3597
3598             /* Grab player ratings from the Creating: message.
3599                Note we have to check for the special case when
3600                the ICS inserts things like [white] or [black]. */
3601             if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3602                 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3603                 /* star_matches:
3604                    0    player 1 name (not necessarily white)
3605                    1    player 1 rating
3606                    2    empty, white, or black (IGNORED)
3607                    3    player 2 name (not necessarily black)
3608                    4    player 2 rating
3609
3610                    The names/ratings are sorted out when the game
3611                    actually starts (below).
3612                 */
3613                 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3614                 player1Rating = string_to_rating(star_match[1]);
3615                 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3616                 player2Rating = string_to_rating(star_match[4]);
3617
3618                 if (appData.debugMode)
3619                   fprintf(debugFP,
3620                           "Ratings from 'Creating:' %s %d, %s %d\n",
3621                           player1Name, player1Rating,
3622                           player2Name, player2Rating);
3623
3624                 continue;
3625             }
3626
3627             /* Improved generic start/end-of-game messages */
3628             if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3629                 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3630                 /* If tkind == 0: */
3631                 /* star_match[0] is the game number */
3632                 /*           [1] is the white player's name */
3633                 /*           [2] is the black player's name */
3634                 /* For end-of-game: */
3635                 /*           [3] is the reason for the game end */
3636                 /*           [4] is a PGN end game-token, preceded by " " */
3637                 /* For start-of-game: */
3638                 /*           [3] begins with "Creating" or "Continuing" */
3639                 /*           [4] is " *" or empty (don't care). */
3640                 int gamenum = atoi(star_match[0]);
3641                 char *whitename, *blackname, *why, *endtoken;
3642                 ChessMove endtype = EndOfFile;
3643
3644                 if (tkind == 0) {
3645                   whitename = star_match[1];
3646                   blackname = star_match[2];
3647                   why = star_match[3];
3648                   endtoken = star_match[4];
3649                 } else {
3650                   whitename = star_match[1];
3651                   blackname = star_match[3];
3652                   why = star_match[5];
3653                   endtoken = star_match[6];
3654                 }
3655
3656                 /* Game start messages */
3657                 if (strncmp(why, "Creating ", 9) == 0 ||
3658                     strncmp(why, "Continuing ", 11) == 0) {
3659                     gs_gamenum = gamenum;
3660                     safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3661                     VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3662 #if ZIPPY
3663                     if (appData.zippyPlay) {
3664                         ZippyGameStart(whitename, blackname);
3665                     }
3666 #endif /*ZIPPY*/
3667                     partnerBoardValid = FALSE; // [HGM] bughouse
3668                     continue;
3669                 }
3670
3671                 /* Game end messages */
3672                 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3673                     ics_gamenum != gamenum) {
3674                     continue;
3675                 }
3676                 while (endtoken[0] == ' ') endtoken++;
3677                 switch (endtoken[0]) {
3678                   case '*':
3679                   default:
3680                     endtype = GameUnfinished;
3681                     break;
3682                   case '0':
3683                     endtype = BlackWins;
3684                     break;
3685                   case '1':
3686                     if (endtoken[1] == '/')
3687                       endtype = GameIsDrawn;
3688                     else
3689                       endtype = WhiteWins;
3690                     break;
3691                 }
3692                 GameEnds(endtype, why, GE_ICS);
3693 #if ZIPPY
3694                 if (appData.zippyPlay && first.initDone) {
3695                     ZippyGameEnd(endtype, why);
3696                     if (first.pr == NULL) {
3697                       /* Start the next process early so that we'll
3698                          be ready for the next challenge */
3699                       StartChessProgram(&first);
3700                     }
3701                     /* Send "new" early, in case this command takes
3702                        a long time to finish, so that we'll be ready
3703                        for the next challenge. */
3704                     gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3705                     Reset(TRUE, TRUE);
3706                 }
3707 #endif /*ZIPPY*/
3708                 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3709                 continue;
3710             }
3711
3712             if (looking_at(buf, &i, "Removing game * from observation") ||
3713                 looking_at(buf, &i, "no longer observing game *") ||
3714                 looking_at(buf, &i, "Game * (*) has no examiners")) {
3715                 if (gameMode == IcsObserving &&
3716                     atoi(star_match[0]) == ics_gamenum)
3717                   {
3718                       /* icsEngineAnalyze */
3719                       if (appData.icsEngineAnalyze) {
3720                             ExitAnalyzeMode();
3721                             ModeHighlight();
3722                       }
3723                       StopClocks();
3724                       gameMode = IcsIdle;
3725                       ics_gamenum = -1;
3726                       ics_user_moved = FALSE;
3727                   }
3728                 continue;
3729             }
3730
3731             if (looking_at(buf, &i, "no longer examining game *")) {
3732                 if (gameMode == IcsExamining &&
3733                     atoi(star_match[0]) == ics_gamenum)
3734                   {
3735                       gameMode = IcsIdle;
3736                       ics_gamenum = -1;
3737                       ics_user_moved = FALSE;
3738                   }
3739                 continue;
3740             }
3741
3742             /* Advance leftover_start past any newlines we find,
3743                so only partial lines can get reparsed */
3744             if (looking_at(buf, &i, "\n")) {
3745                 prevColor = curColor;
3746                 if (curColor != ColorNormal) {
3747                     if (oldi > next_out) {
3748                         SendToPlayer(&buf[next_out], oldi - next_out);
3749                         next_out = oldi;
3750                     }
3751                     Colorize(ColorNormal, FALSE);
3752                     curColor = ColorNormal;
3753                 }
3754                 if (started == STARTED_BOARD) {
3755                     started = STARTED_NONE;
3756                     parse[parse_pos] = NULLCHAR;
3757                     ParseBoard12(parse);
3758                     ics_user_moved = 0;
3759
3760                     /* Send premove here */
3761                     if (appData.premove) {
3762                       char str[MSG_SIZ];
3763                       if (currentMove == 0 &&
3764                           gameMode == IcsPlayingWhite &&
3765                           appData.premoveWhite) {
3766                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3767                         if (appData.debugMode)
3768                           fprintf(debugFP, "Sending premove:\n");
3769                         SendToICS(str);
3770                       } else if (currentMove == 1 &&
3771                                  gameMode == IcsPlayingBlack &&
3772                                  appData.premoveBlack) {
3773                         snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3774                         if (appData.debugMode)
3775                           fprintf(debugFP, "Sending premove:\n");
3776                         SendToICS(str);
3777                       } else if (gotPremove) {
3778                         gotPremove = 0;
3779                         ClearPremoveHighlights();
3780                         if (appData.debugMode)
3781                           fprintf(debugFP, "Sending premove:\n");
3782                           UserMoveEvent(premoveFromX, premoveFromY,
3783                                         premoveToX, premoveToY,
3784                                         premovePromoChar);
3785                       }
3786                     }
3787
3788                     /* Usually suppress following prompt */
3789                     if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3790                         while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3791                         if (looking_at(buf, &i, "*% ")) {
3792                             savingComment = FALSE;
3793                             suppressKibitz = 0;
3794                         }
3795                     }
3796                     next_out = i;
3797                 } else if (started == STARTED_HOLDINGS) {
3798                     int gamenum;
3799                     char new_piece[MSG_SIZ];
3800                     started = STARTED_NONE;
3801                     parse[parse_pos] = NULLCHAR;
3802                     if (appData.debugMode)
3803                       fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3804                                                         parse, currentMove);
3805                     if (sscanf(parse, " game %d", &gamenum) == 1) {
3806                       if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3807                         if (gameInfo.variant == VariantNormal) {
3808                           /* [HGM] We seem to switch variant during a game!
3809                            * Presumably no holdings were displayed, so we have
3810                            * to move the position two files to the right to
3811                            * create room for them!
3812                            */
3813                           VariantClass newVariant;
3814                           switch(gameInfo.boardWidth) { // base guess on board width
3815                                 case 9:  newVariant = VariantShogi; break;
3816                                 case 10: newVariant = VariantGreat; break;
3817                                 default: newVariant = VariantCrazyhouse; break;
3818                           }
3819                           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3820                           /* Get a move list just to see the header, which
3821                              will tell us whether this is really bug or zh */
3822                           if (ics_getting_history == H_FALSE) {
3823                             ics_getting_history = H_REQUESTED;
3824                             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3825                             SendToICS(str);
3826                           }
3827                         }
3828                         new_piece[0] = NULLCHAR;
3829                         sscanf(parse, "game %d white [%s black [%s <- %s",
3830                                &gamenum, white_holding, black_holding,
3831                                new_piece);
3832                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3833                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3834                         /* [HGM] copy holdings to board holdings area */
3835                         CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3836                         CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3837                         boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3838 #if ZIPPY
3839                         if (appData.zippyPlay && first.initDone) {
3840                             ZippyHoldings(white_holding, black_holding,
3841                                           new_piece);
3842                         }
3843 #endif /*ZIPPY*/
3844                         if (tinyLayout || smallLayout) {
3845                             char wh[16], bh[16];
3846                             PackHolding(wh, white_holding);
3847                             PackHolding(bh, black_holding);
3848                             snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3849                                     gameInfo.white, gameInfo.black);
3850                         } else {
3851                           snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3852                                     gameInfo.white, white_holding,
3853                                     gameInfo.black, black_holding);
3854                         }
3855                         if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3856                         DrawPosition(FALSE, boards[currentMove]);
3857                         DisplayTitle(str);
3858                       } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3859                         sscanf(parse, "game %d white [%s black [%s <- %s",
3860                                &gamenum, white_holding, black_holding,
3861                                new_piece);
3862                         white_holding[strlen(white_holding)-1] = NULLCHAR;
3863                         black_holding[strlen(black_holding)-1] = NULLCHAR;
3864                         /* [HGM] copy holdings to partner-board holdings area */
3865                         CopyHoldings(partnerBoard, white_holding, WhitePawn);
3866                         CopyHoldings(partnerBoard, black_holding, BlackPawn);
3867                         if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
3868                         if(partnerUp) DrawPosition(FALSE, partnerBoard);
3869                         if(twoBoards) { partnerUp = 0; flipView = !flipView; }
3870                       }
3871                     }
3872                     /* Suppress following prompt */
3873                     if (looking_at(buf, &i, "*% ")) {
3874                         if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
3875                         savingComment = FALSE;
3876                         suppressKibitz = 0;
3877                     }
3878                     next_out = i;
3879                 }
3880                 continue;
3881             }
3882
3883             i++;                /* skip unparsed character and loop back */
3884         }
3885
3886         if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
3887 //          started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
3888 //          SendToPlayer(&buf[next_out], i - next_out);
3889             started != STARTED_HOLDINGS && leftover_start > next_out) {
3890             SendToPlayer(&buf[next_out], leftover_start - next_out);
3891             next_out = i;
3892         }
3893
3894         leftover_len = buf_len - leftover_start;
3895         /* if buffer ends with something we couldn't parse,
3896            reparse it after appending the next read */
3897
3898     } else if (count == 0) {
3899         RemoveInputSource(isr);
3900         DisplayFatalError(_("Connection closed by ICS"), 0, 0);
3901     } else {
3902         DisplayFatalError(_("Error reading from ICS"), error, 1);
3903     }
3904 }
3905
3906
3907 /* Board style 12 looks like this:
3908
3909    <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
3910
3911  * The "<12> " is stripped before it gets to this routine.  The two
3912  * trailing 0's (flip state and clock ticking) are later addition, and
3913  * some chess servers may not have them, or may have only the first.
3914  * Additional trailing fields may be added in the future.
3915  */
3916
3917 #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"
3918
3919 #define RELATION_OBSERVING_PLAYED    0
3920 #define RELATION_OBSERVING_STATIC   -2   /* examined, oldmoves, or smoves */
3921 #define RELATION_PLAYING_MYMOVE      1
3922 #define RELATION_PLAYING_NOTMYMOVE  -1
3923 #define RELATION_EXAMINING           2
3924 #define RELATION_ISOLATED_BOARD     -3
3925 #define RELATION_STARTING_POSITION  -4   /* FICS only */
3926
3927 void
3928 ParseBoard12(string)
3929      char *string;
3930 {
3931     GameMode newGameMode;
3932     int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
3933     int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
3934     int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
3935     char to_play, board_chars[200];
3936     char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
3937     char black[32], white[32];
3938     Board board;
3939     int prevMove = currentMove;
3940     int ticking = 2;
3941     ChessMove moveType;
3942     int fromX, fromY, toX, toY;
3943     char promoChar;
3944     int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
3945     char *bookHit = NULL; // [HGM] book
3946     Boolean weird = FALSE, reqFlag = FALSE;
3947
3948     fromX = fromY = toX = toY = -1;
3949
3950     newGame = FALSE;
3951
3952     if (appData.debugMode)
3953       fprintf(debugFP, _("Parsing board: %s\n"), string);
3954
3955     move_str[0] = NULLCHAR;
3956     elapsed_time[0] = NULLCHAR;
3957     {   /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
3958         int  i = 0, j;
3959         while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
3960             if(string[i] == ' ') { ranks++; files = 0; }
3961             else files++;
3962             if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
3963             i++;
3964         }
3965         for(j = 0; j <i; j++) board_chars[j] = string[j];
3966         board_chars[i] = '\0';
3967         string += i + 1;
3968     }
3969     n = sscanf(string, PATTERN, &to_play, &double_push,
3970                &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
3971                &gamenum, white, black, &relation, &basetime, &increment,
3972                &white_stren, &black_stren, &white_time, &black_time,
3973                &moveNum, str, elapsed_time, move_str, &ics_flip,
3974                &ticking);
3975
3976     if (n < 21) {
3977         snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
3978         DisplayError(str, 0);
3979         return;
3980     }
3981
3982     /* Convert the move number to internal form */
3983     moveNum = (moveNum - 1) * 2;
3984     if (to_play == 'B') moveNum++;
3985     if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
3986       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
3987                         0, 1);
3988       return;
3989     }
3990
3991     switch (relation) {
3992       case RELATION_OBSERVING_PLAYED:
3993       case RELATION_OBSERVING_STATIC:
3994         if (gamenum == -1) {
3995             /* Old ICC buglet */
3996             relation = RELATION_OBSERVING_STATIC;
3997         }
3998         newGameMode = IcsObserving;
3999         break;
4000       case RELATION_PLAYING_MYMOVE:
4001       case RELATION_PLAYING_NOTMYMOVE:
4002         newGameMode =
4003           ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4004             IcsPlayingWhite : IcsPlayingBlack;
4005         break;
4006       case RELATION_EXAMINING:
4007         newGameMode = IcsExamining;
4008         break;
4009       case RELATION_ISOLATED_BOARD:
4010       default:
4011         /* Just display this board.  If user was doing something else,
4012            we will forget about it until the next board comes. */
4013         newGameMode = IcsIdle;
4014         break;
4015       case RELATION_STARTING_POSITION:
4016         newGameMode = gameMode;
4017         break;
4018     }
4019
4020     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4021          && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4022       // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4023       char *toSqr;
4024       for (k = 0; k < ranks; k++) {
4025         for (j = 0; j < files; j++)
4026           board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4027         if(gameInfo.holdingsWidth > 1) {
4028              board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4029              board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4030         }
4031       }
4032       CopyBoard(partnerBoard, board);
4033       if(toSqr = strchr(str, '/')) { // extract highlights from long move
4034         partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4035         partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4036       } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4037       if(toSqr = strchr(str, '-')) {
4038         partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4039         partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4040       } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4041       if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4042       if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4043       if(partnerUp) DrawPosition(FALSE, partnerBoard);
4044       if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4045       snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4046                  (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4047       DisplayMessage(partnerStatus, "");
4048         partnerBoardValid = TRUE;
4049       return;
4050     }
4051
4052     /* Modify behavior for initial board display on move listing
4053        of wild games.
4054        */
4055     switch (ics_getting_history) {
4056       case H_FALSE:
4057       case H_REQUESTED:
4058         break;
4059       case H_GOT_REQ_HEADER:
4060       case H_GOT_UNREQ_HEADER:
4061         /* This is the initial position of the current game */
4062         gamenum = ics_gamenum;
4063         moveNum = 0;            /* old ICS bug workaround */
4064         if (to_play == 'B') {
4065           startedFromSetupPosition = TRUE;
4066           blackPlaysFirst = TRUE;
4067           moveNum = 1;
4068           if (forwardMostMove == 0) forwardMostMove = 1;
4069           if (backwardMostMove == 0) backwardMostMove = 1;
4070           if (currentMove == 0) currentMove = 1;
4071         }
4072         newGameMode = gameMode;
4073         relation = RELATION_STARTING_POSITION; /* ICC needs this */
4074         break;
4075       case H_GOT_UNWANTED_HEADER:
4076         /* This is an initial board that we don't want */
4077         return;
4078       case H_GETTING_MOVES:
4079         /* Should not happen */
4080         DisplayError(_("Error gathering move list: extra board"), 0);
4081         ics_getting_history = H_FALSE;
4082         return;
4083     }
4084
4085    if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4086                                         weird && (int)gameInfo.variant < (int)VariantShogi) {
4087      /* [HGM] We seem to have switched variant unexpectedly
4088       * Try to guess new variant from board size
4089       */
4090           VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4091           if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4092           if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4093           if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4094           if(ranks == 9 && files == 9)  newVariant = VariantShogi; else
4095           if(!weird) newVariant = VariantNormal;
4096           VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4097           /* Get a move list just to see the header, which
4098              will tell us whether this is really bug or zh */
4099           if (ics_getting_history == H_FALSE) {
4100             ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4101             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4102             SendToICS(str);
4103           }
4104     }
4105
4106     /* Take action if this is the first board of a new game, or of a
4107        different game than is currently being displayed.  */
4108     if (gamenum != ics_gamenum || newGameMode != gameMode ||
4109         relation == RELATION_ISOLATED_BOARD) {
4110
4111         /* Forget the old game and get the history (if any) of the new one */
4112         if (gameMode != BeginningOfGame) {
4113           Reset(TRUE, TRUE);
4114         }
4115         newGame = TRUE;
4116         if (appData.autoRaiseBoard) BoardToTop();
4117         prevMove = -3;
4118         if (gamenum == -1) {
4119             newGameMode = IcsIdle;
4120         } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4121                    appData.getMoveList && !reqFlag) {
4122             /* Need to get game history */
4123             ics_getting_history = H_REQUESTED;
4124             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4125             SendToICS(str);
4126         }
4127
4128         /* Initially flip the board to have black on the bottom if playing
4129            black or if the ICS flip flag is set, but let the user change
4130            it with the Flip View button. */
4131         flipView = appData.autoFlipView ?
4132           (newGameMode == IcsPlayingBlack) || ics_flip :
4133           appData.flipView;
4134
4135         /* Done with values from previous mode; copy in new ones */
4136         gameMode = newGameMode;
4137         ModeHighlight();
4138         ics_gamenum = gamenum;
4139         if (gamenum == gs_gamenum) {
4140             int klen = strlen(gs_kind);
4141             if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4142             snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4143             gameInfo.event = StrSave(str);
4144         } else {
4145             gameInfo.event = StrSave("ICS game");
4146         }
4147         gameInfo.site = StrSave(appData.icsHost);
4148         gameInfo.date = PGNDate();
4149         gameInfo.round = StrSave("-");
4150         gameInfo.white = StrSave(white);
4151         gameInfo.black = StrSave(black);
4152         timeControl = basetime * 60 * 1000;
4153         timeControl_2 = 0;
4154         timeIncrement = increment * 1000;
4155         movesPerSession = 0;
4156         gameInfo.timeControl = TimeControlTagValue();
4157         VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4158   if (appData.debugMode) {
4159     fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4160     fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4161     setbuf(debugFP, NULL);
4162   }
4163
4164         gameInfo.outOfBook = NULL;
4165
4166         /* Do we have the ratings? */
4167         if (strcmp(player1Name, white) == 0 &&
4168             strcmp(player2Name, black) == 0) {
4169             if (appData.debugMode)
4170               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4171                       player1Rating, player2Rating);
4172             gameInfo.whiteRating = player1Rating;
4173             gameInfo.blackRating = player2Rating;
4174         } else if (strcmp(player2Name, white) == 0 &&
4175                    strcmp(player1Name, black) == 0) {
4176             if (appData.debugMode)
4177               fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4178                       player2Rating, player1Rating);
4179             gameInfo.whiteRating = player2Rating;
4180             gameInfo.blackRating = player1Rating;
4181         }
4182         player1Name[0] = player2Name[0] = NULLCHAR;
4183
4184         /* Silence shouts if requested */
4185         if (appData.quietPlay &&
4186             (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4187             SendToICS(ics_prefix);
4188             SendToICS("set shout 0\n");
4189         }
4190     }
4191
4192     /* Deal with midgame name changes */
4193     if (!newGame) {
4194         if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4195             if (gameInfo.white) free(gameInfo.white);
4196             gameInfo.white = StrSave(white);
4197         }
4198         if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4199             if (gameInfo.black) free(gameInfo.black);
4200             gameInfo.black = StrSave(black);
4201         }
4202     }
4203
4204     /* Throw away game result if anything actually changes in examine mode */
4205     if (gameMode == IcsExamining && !newGame) {
4206         gameInfo.result = GameUnfinished;
4207         if (gameInfo.resultDetails != NULL) {
4208             free(gameInfo.resultDetails);
4209             gameInfo.resultDetails = NULL;
4210         }
4211     }
4212
4213     /* In pausing && IcsExamining mode, we ignore boards coming
4214        in if they are in a different variation than we are. */
4215     if (pauseExamInvalid) return;
4216     if (pausing && gameMode == IcsExamining) {
4217         if (moveNum <= pauseExamForwardMostMove) {
4218             pauseExamInvalid = TRUE;
4219             forwardMostMove = pauseExamForwardMostMove;
4220             return;
4221         }
4222     }
4223
4224   if (appData.debugMode) {
4225     fprintf(debugFP, "load %dx%d board\n", files, ranks);
4226   }
4227     /* Parse the board */
4228     for (k = 0; k < ranks; k++) {
4229       for (j = 0; j < files; j++)
4230         board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4231       if(gameInfo.holdingsWidth > 1) {
4232            board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4233            board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4234       }
4235     }
4236     CopyBoard(boards[moveNum], board);
4237     boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4238     if (moveNum == 0) {
4239         startedFromSetupPosition =
4240           !CompareBoards(board, initialPosition);
4241         if(startedFromSetupPosition)
4242             initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4243     }
4244
4245     /* [HGM] Set castling rights. Take the outermost Rooks,
4246        to make it also work for FRC opening positions. Note that board12
4247        is really defective for later FRC positions, as it has no way to
4248        indicate which Rook can castle if they are on the same side of King.
4249        For the initial position we grant rights to the outermost Rooks,
4250        and remember thos rights, and we then copy them on positions
4251        later in an FRC game. This means WB might not recognize castlings with
4252        Rooks that have moved back to their original position as illegal,
4253        but in ICS mode that is not its job anyway.
4254     */
4255     if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4256     { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4257
4258         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4259             if(board[0][i] == WhiteRook) j = i;
4260         initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4261         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4262             if(board[0][i] == WhiteRook) j = i;
4263         initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4264         for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4265             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4266         initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4267         for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4268             if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4269         initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4270
4271         if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4272         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4273             if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4274         for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4275             if(board[BOARD_HEIGHT-1][k] == bKing)
4276                 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4277         if(gameInfo.variant == VariantTwoKings) {
4278             // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4279             if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4280             if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4281         }
4282     } else { int r;
4283         r = boards[moveNum][CASTLING][0] = initialRights[0];
4284         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4285         r = boards[moveNum][CASTLING][1] = initialRights[1];
4286         if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4287         r = boards[moveNum][CASTLING][3] = initialRights[3];
4288         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4289         r = boards[moveNum][CASTLING][4] = initialRights[4];
4290         if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4291         /* wildcastle kludge: always assume King has rights */
4292         r = boards[moveNum][CASTLING][2] = initialRights[2];
4293         r = boards[moveNum][CASTLING][5] = initialRights[5];
4294     }
4295     /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4296     boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4297
4298
4299     if (ics_getting_history == H_GOT_REQ_HEADER ||
4300         ics_getting_history == H_GOT_UNREQ_HEADER) {
4301         /* This was an initial position from a move list, not
4302            the current position */
4303         return;
4304     }
4305
4306     /* Update currentMove and known move number limits */
4307     newMove = newGame || moveNum > forwardMostMove;
4308
4309     if (newGame) {
4310         forwardMostMove = backwardMostMove = currentMove = moveNum;
4311         if (gameMode == IcsExamining && moveNum == 0) {
4312           /* Workaround for ICS limitation: we are not told the wild
4313              type when starting to examine a game.  But if we ask for
4314              the move list, the move list header will tell us */
4315             ics_getting_history = H_REQUESTED;
4316             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4317             SendToICS(str);
4318         }
4319     } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4320                || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4321 #if ZIPPY
4322         /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4323         /* [HGM] applied this also to an engine that is silently watching        */
4324         if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4325             (gameMode == IcsObserving || gameMode == IcsExamining) &&
4326             gameInfo.variant == currentlyInitializedVariant) {
4327           takeback = forwardMostMove - moveNum;
4328           for (i = 0; i < takeback; i++) {
4329             if (appData.debugMode) fprintf(debugFP, "take back move\n");
4330             SendToProgram("undo\n", &first);
4331           }
4332         }
4333 #endif
4334
4335         forwardMostMove = moveNum;
4336         if (!pausing || currentMove > forwardMostMove)
4337           currentMove = forwardMostMove;
4338     } else {
4339         /* New part of history that is not contiguous with old part */
4340         if (pausing && gameMode == IcsExamining) {
4341             pauseExamInvalid = TRUE;
4342             forwardMostMove = pauseExamForwardMostMove;
4343             return;
4344         }
4345         if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4346 #if ZIPPY
4347             if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4348                 // [HGM] when we will receive the move list we now request, it will be
4349                 // fed to the engine from the first move on. So if the engine is not
4350                 // in the initial position now, bring it there.
4351                 InitChessProgram(&first, 0);
4352             }
4353 #endif
4354             ics_getting_history = H_REQUESTED;
4355             snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4356             SendToICS(str);
4357         }
4358         forwardMostMove = backwardMostMove = currentMove = moveNum;
4359     }
4360
4361     /* Update the clocks */
4362     if (strchr(elapsed_time, '.')) {
4363       /* Time is in ms */
4364       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4365       timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4366     } else {
4367       /* Time is in seconds */
4368       timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4369       timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4370     }
4371
4372
4373 #if ZIPPY
4374     if (appData.zippyPlay && newGame &&
4375         gameMode != IcsObserving && gameMode != IcsIdle &&
4376         gameMode != IcsExamining)
4377       ZippyFirstBoard(moveNum, basetime, increment);
4378 #endif
4379
4380     /* Put the move on the move list, first converting
4381        to canonical algebraic form. */
4382     if (moveNum > 0) {
4383   if (appData.debugMode) {
4384     if (appData.debugMode) { int f = forwardMostMove;
4385         fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4386                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4387                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4388     }
4389     fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4390     fprintf(debugFP, "moveNum = %d\n", moveNum);
4391     fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4392     setbuf(debugFP, NULL);
4393   }
4394         if (moveNum <= backwardMostMove) {
4395             /* We don't know what the board looked like before
4396                this move.  Punt. */
4397           safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4398             strcat(parseList[moveNum - 1], " ");
4399             strcat(parseList[moveNum - 1], elapsed_time);
4400             moveList[moveNum - 1][0] = NULLCHAR;
4401         } else if (strcmp(move_str, "none") == 0) {
4402             // [HGM] long SAN: swapped order; test for 'none' before parsing move
4403             /* Again, we don't know what the board looked like;
4404                this is really the start of the game. */
4405             parseList[moveNum - 1][0] = NULLCHAR;
4406             moveList[moveNum - 1][0] = NULLCHAR;
4407             backwardMostMove = moveNum;
4408             startedFromSetupPosition = TRUE;
4409             fromX = fromY = toX = toY = -1;
4410         } else {
4411           // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4412           //                 So we parse the long-algebraic move string in stead of the SAN move
4413           int valid; char buf[MSG_SIZ], *prom;
4414
4415           if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4416                 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4417           // str looks something like "Q/a1-a2"; kill the slash
4418           if(str[1] == '/')
4419             snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4420           else  safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4421           if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4422                 strcat(buf, prom); // long move lacks promo specification!
4423           if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4424                 if(appData.debugMode)
4425                         fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4426                 safeStrCpy(move_str, buf, MSG_SIZ);
4427           }
4428           valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4429                                 &fromX, &fromY, &toX, &toY, &promoChar)
4430                || ParseOneMove(buf, moveNum - 1, &moveType,
4431                                 &fromX, &fromY, &toX, &toY, &promoChar);
4432           // end of long SAN patch
4433           if (valid) {
4434             (void) CoordsToAlgebraic(boards[moveNum - 1],
4435                                      PosFlags(moveNum - 1),
4436                                      fromY, fromX, toY, toX, promoChar,
4437                                      parseList[moveNum-1]);
4438             switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4439               case MT_NONE:
4440               case MT_STALEMATE:
4441               default:
4442                 break;
4443               case MT_CHECK:
4444                 if(gameInfo.variant != VariantShogi)
4445                     strcat(parseList[moveNum - 1], "+");
4446                 break;
4447               case MT_CHECKMATE:
4448               case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4449                 strcat(parseList[moveNum - 1], "#");
4450                 break;
4451             }
4452             strcat(parseList[moveNum - 1], " ");
4453             strcat(parseList[moveNum - 1], elapsed_time);
4454             /* currentMoveString is set as a side-effect of ParseOneMove */
4455             if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4456             safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4457             strcat(moveList[moveNum - 1], "\n");
4458
4459             if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4460                                  && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4461               for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4462                 ChessSquare old, new = boards[moveNum][k][j];
4463                   if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4464                   old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4465                   if(old == new) continue;
4466                   if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4467                   else if(new == WhiteWazir || new == BlackWazir) {
4468                       if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4469                            boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4470                       else boards[moveNum][k][j] = old; // preserve type of Gold
4471                   } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4472                       boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4473               }
4474           } else {
4475             /* Move from ICS was illegal!?  Punt. */
4476             if (appData.debugMode) {
4477               fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4478               fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4479             }
4480             safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4481             strcat(parseList[moveNum - 1], " ");
4482             strcat(parseList[moveNum - 1], elapsed_time);
4483             moveList[moveNum - 1][0] = NULLCHAR;
4484             fromX = fromY = toX = toY = -1;
4485           }
4486         }
4487   if (appData.debugMode) {
4488     fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4489     setbuf(debugFP, NULL);
4490   }
4491
4492 #if ZIPPY
4493         /* Send move to chess program (BEFORE animating it). */
4494         if (appData.zippyPlay && !newGame && newMove &&
4495            (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4496
4497             if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4498                 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4499                 if (moveList[moveNum - 1][0] == NULLCHAR) {
4500                   snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4501                             move_str);
4502                     DisplayError(str, 0);
4503                 } else {
4504                     if (first.sendTime) {
4505                         SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4506                     }
4507                     bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4508                     if (firstMove && !bookHit) {
4509                         firstMove = FALSE;
4510                         if (first.useColors) {
4511                           SendToProgram(gameMode == IcsPlayingWhite ?
4512                                         "white\ngo\n" :
4513                                         "black\ngo\n", &first);
4514                         } else {
4515                           SendToProgram("go\n", &first);
4516                         }
4517                         first.maybeThinking = TRUE;
4518                     }
4519                 }
4520             } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4521               if (moveList[moveNum - 1][0] == NULLCHAR) {
4522                 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4523                 DisplayError(str, 0);
4524               } else {
4525                 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4526                 SendMoveToProgram(moveNum - 1, &first);
4527               }
4528             }
4529         }
4530 #endif
4531     }
4532
4533     if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4534         /* If move comes from a remote source, animate it.  If it
4535            isn't remote, it will have already been animated. */
4536         if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4537             AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4538         }
4539         if (!pausing && appData.highlightLastMove) {
4540             SetHighlights(fromX, fromY, toX, toY);
4541         }
4542     }
4543
4544     /* Start the clocks */
4545     whiteFlag = blackFlag = FALSE;
4546     appData.clockMode = !(basetime == 0 && increment == 0);
4547     if (ticking == 0) {
4548       ics_clock_paused = TRUE;
4549       StopClocks();
4550     } else if (ticking == 1) {
4551       ics_clock_paused = FALSE;
4552     }
4553     if (gameMode == IcsIdle ||
4554         relation == RELATION_OBSERVING_STATIC ||
4555         relation == RELATION_EXAMINING ||
4556         ics_clock_paused)
4557       DisplayBothClocks();
4558     else
4559       StartClocks();
4560
4561     /* Display opponents and material strengths */
4562     if (gameInfo.variant != VariantBughouse &&
4563         gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4564         if (tinyLayout || smallLayout) {
4565             if(gameInfo.variant == VariantNormal)
4566               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4567                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4568                     basetime, increment);
4569             else
4570               snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4571                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4572                     basetime, increment, (int) gameInfo.variant);
4573         } else {
4574             if(gameInfo.variant == VariantNormal)
4575               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4576                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4577                     basetime, increment);
4578             else
4579               snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4580                     gameInfo.white, white_stren, gameInfo.black, black_stren,
4581                     basetime, increment, VariantName(gameInfo.variant));
4582         }
4583         DisplayTitle(str);
4584   if (appData.debugMode) {
4585     fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4586   }
4587     }
4588
4589
4590     /* Display the board */
4591     if (!pausing && !appData.noGUI) {
4592
4593       if (appData.premove)
4594           if (!gotPremove ||
4595              ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4596              ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4597               ClearPremoveHighlights();
4598
4599       j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4600         if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4601       DrawPosition(j, boards[currentMove]);
4602
4603       DisplayMove(moveNum - 1);
4604       if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4605             !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4606               (gameMode == IcsPlayingBlack) &&  (WhiteOnMove(moveNum))   ) ) {
4607         if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4608       }
4609     }
4610
4611     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4612 #if ZIPPY
4613     if(bookHit) { // [HGM] book: simulate book reply
4614         static char bookMove[MSG_SIZ]; // a bit generous?
4615
4616         programStats.nodes = programStats.depth = programStats.time =
4617         programStats.score = programStats.got_only_move = 0;
4618         sprintf(programStats.movelist, "%s (xbook)", bookHit);
4619
4620         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4621         strcat(bookMove, bookHit);
4622         HandleMachineMove(bookMove, &first);
4623     }
4624 #endif
4625 }
4626
4627 void
4628 GetMoveListEvent()
4629 {
4630     char buf[MSG_SIZ];
4631     if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4632         ics_getting_history = H_REQUESTED;
4633         snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4634         SendToICS(buf);
4635     }
4636 }
4637
4638 void
4639 AnalysisPeriodicEvent(force)
4640      int force;
4641 {
4642     if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4643          && !force) || !appData.periodicUpdates)
4644       return;
4645
4646     /* Send . command to Crafty to collect stats */
4647     SendToProgram(".\n", &first);
4648
4649     /* Don't send another until we get a response (this makes
4650        us stop sending to old Crafty's which don't understand
4651        the "." command (sending illegal cmds resets node count & time,
4652        which looks bad)) */
4653     programStats.ok_to_send = 0;
4654 }
4655
4656 void ics_update_width(new_width)
4657         int new_width;
4658 {
4659         ics_printf("set width %d\n", new_width);
4660 }
4661
4662 void
4663 SendMoveToProgram(moveNum, cps)
4664      int moveNum;
4665      ChessProgramState *cps;
4666 {
4667     char buf[MSG_SIZ];
4668
4669     if (cps->useUsermove) {
4670       SendToProgram("usermove ", cps);
4671     }
4672     if (cps->useSAN) {
4673       char *space;
4674       if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4675         int len = space - parseList[moveNum];
4676         memcpy(buf, parseList[moveNum], len);
4677         buf[len++] = '\n';
4678         buf[len] = NULLCHAR;
4679       } else {
4680         snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4681       }
4682       SendToProgram(buf, cps);
4683     } else {
4684       if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4685         AlphaRank(moveList[moveNum], 4);
4686         SendToProgram(moveList[moveNum], cps);
4687         AlphaRank(moveList[moveNum], 4); // and back
4688       } else
4689       /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4690        * the engine. It would be nice to have a better way to identify castle
4691        * moves here. */
4692       if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4693                                                                          && cps->useOOCastle) {
4694         int fromX = moveList[moveNum][0] - AAA;
4695         int fromY = moveList[moveNum][1] - ONE;
4696         int toX = moveList[moveNum][2] - AAA;
4697         int toY = moveList[moveNum][3] - ONE;
4698         if((boards[moveNum][fromY][fromX] == WhiteKing
4699             && boards[moveNum][toY][toX] == WhiteRook)
4700            || (boards[moveNum][fromY][fromX] == BlackKing
4701                && boards[moveNum][toY][toX] == BlackRook)) {
4702           if(toX > fromX) SendToProgram("O-O\n", cps);
4703           else SendToProgram("O-O-O\n", cps);
4704         }
4705         else SendToProgram(moveList[moveNum], cps);
4706       }
4707       else SendToProgram(moveList[moveNum], cps);
4708       /* End of additions by Tord */
4709     }
4710
4711     /* [HGM] setting up the opening has brought engine in force mode! */
4712     /*       Send 'go' if we are in a mode where machine should play. */
4713     if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4714         (gameMode == TwoMachinesPlay   ||
4715 #if ZIPPY
4716          gameMode == IcsPlayingBlack     || gameMode == IcsPlayingWhite ||
4717 #endif
4718          gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4719         SendToProgram("go\n", cps);
4720   if (appData.debugMode) {
4721     fprintf(debugFP, "(extra)\n");
4722   }
4723     }
4724     setboardSpoiledMachineBlack = 0;
4725 }
4726
4727 void
4728 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4729      ChessMove moveType;
4730      int fromX, fromY, toX, toY;
4731      char promoChar;
4732 {
4733     char user_move[MSG_SIZ];
4734
4735     switch (moveType) {
4736       default:
4737         snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4738                 (int)moveType, fromX, fromY, toX, toY);
4739         DisplayError(user_move + strlen("say "), 0);
4740         break;
4741       case WhiteKingSideCastle:
4742       case BlackKingSideCastle:
4743       case WhiteQueenSideCastleWild:
4744       case BlackQueenSideCastleWild:
4745       /* PUSH Fabien */
4746       case WhiteHSideCastleFR:
4747       case BlackHSideCastleFR:
4748       /* POP Fabien */
4749         snprintf(user_move, MSG_SIZ, "o-o\n");
4750         break;
4751       case WhiteQueenSideCastle:
4752       case BlackQueenSideCastle:
4753       case WhiteKingSideCastleWild:
4754       case BlackKingSideCastleWild:
4755       /* PUSH Fabien */
4756       case WhiteASideCastleFR:
4757       case BlackASideCastleFR:
4758       /* POP Fabien */
4759         snprintf(user_move, MSG_SIZ, "o-o-o\n");
4760         break;
4761       case WhiteNonPromotion:
4762       case BlackNonPromotion:
4763         sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4764         break;
4765       case WhitePromotion:
4766       case BlackPromotion:
4767         if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4768           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4769                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4770                 PieceToChar(WhiteFerz));
4771         else if(gameInfo.variant == VariantGreat)
4772           snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4773                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4774                 PieceToChar(WhiteMan));
4775         else
4776           snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4777                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4778                 promoChar);
4779         break;
4780       case WhiteDrop:
4781       case BlackDrop:
4782       drop:
4783         snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4784                  ToUpper(PieceToChar((ChessSquare) fromX)),
4785                  AAA + toX, ONE + toY);
4786         break;
4787       case IllegalMove:  /* could be a variant we don't quite understand */
4788         if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4789       case NormalMove:
4790       case WhiteCapturesEnPassant:
4791       case BlackCapturesEnPassant:
4792         snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4793                 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4794         break;
4795     }
4796     SendToICS(user_move);
4797     if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4798         ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4799 }
4800
4801 void
4802 UploadGameEvent()
4803 {   // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4804     int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4805     static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4806     if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4807         DisplayError("You cannot do this while you are playing or observing", 0);
4808         return;
4809     }
4810     if(gameMode != IcsExamining) { // is this ever not the case?
4811         char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4812
4813         if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4814           snprintf(command,MSG_SIZ, "match %s", ics_handle);
4815         } else { // on FICS we must first go to general examine mode
4816           safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4817         }
4818         if(gameInfo.variant != VariantNormal) {
4819             // try figure out wild number, as xboard names are not always valid on ICS
4820             for(i=1; i<=36; i++) {
4821               snprintf(buf, MSG_SIZ, "wild/%d", i);
4822                 if(StringToVariant(buf) == gameInfo.variant) break;
4823             }
4824             if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4825             else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4826             else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4827         } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4828         SendToICS(ics_prefix);
4829         SendToICS(buf);
4830         if(startedFromSetupPosition || backwardMostMove != 0) {
4831           fen = PositionToFEN(backwardMostMove, NULL);
4832           if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4833             snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4834             SendToICS(buf);
4835           } else { // FICS: everything has to set by separate bsetup commands
4836             p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4837             snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4838             SendToICS(buf);
4839             if(!WhiteOnMove(backwardMostMove)) {
4840                 SendToICS("bsetup tomove black\n");
4841             }
4842             i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4843             snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4844             SendToICS(buf);
4845             i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4846             snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4847             SendToICS(buf);
4848             i = boards[backwardMostMove][EP_STATUS];
4849             if(i >= 0) { // set e.p.
4850               snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4851                 SendToICS(buf);
4852             }
4853             bsetup++;
4854           }
4855         }
4856       if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4857             SendToICS("bsetup done\n"); // switch to normal examining.
4858     }
4859     for(i = backwardMostMove; i<last; i++) {
4860         char buf[20];
4861         snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
4862         SendToICS(buf);
4863     }
4864     SendToICS(ics_prefix);
4865     SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
4866 }
4867
4868 void
4869 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
4870      int rf, ff, rt, ft;
4871      char promoChar;
4872      char move[7];
4873 {
4874     if (rf == DROP_RANK) {
4875       sprintf(move, "%c@%c%c\n",
4876                 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
4877     } else {
4878         if (promoChar == 'x' || promoChar == NULLCHAR) {
4879           sprintf(move, "%c%c%c%c\n",
4880                     AAA + ff, ONE + rf, AAA + ft, ONE + rt);
4881         } else {
4882             sprintf(move, "%c%c%c%c%c\n",
4883                     AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
4884         }
4885     }
4886 }
4887
4888 void
4889 ProcessICSInitScript(f)
4890      FILE *f;
4891 {
4892     char buf[MSG_SIZ];
4893
4894     while (fgets(buf, MSG_SIZ, f)) {
4895         SendToICSDelayed(buf,(long)appData.msLoginDelay);
4896     }
4897
4898     fclose(f);
4899 }
4900
4901
4902 static int lastX, lastY, selectFlag, dragging;
4903
4904 void
4905 Sweep(int step)
4906 {
4907     ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
4908     if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
4909     if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
4910     if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
4911     if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
4912     if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
4913     do {
4914         promoSweep -= step;
4915         if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
4916         else if((int)promoSweep == -1) promoSweep = WhiteKing;
4917         else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
4918         else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
4919         if(!step) step = 1;
4920     } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
4921             appData.testLegality && (promoSweep == king ||
4922             gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
4923     ChangeDragPiece(promoSweep);
4924 }
4925
4926 int PromoScroll(int x, int y)
4927 {
4928   int step = 0;
4929
4930   if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
4931   if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
4932   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
4933   if(!step) return FALSE;
4934   lastX = x; lastY = y;
4935   if((promoSweep < BlackPawn) == flipView) step = -step;
4936   if(step > 0) selectFlag = 1;
4937   if(!selectFlag) Sweep(step);
4938   return FALSE;
4939 }
4940
4941 void
4942 NextPiece(int step)
4943 {
4944     ChessSquare piece = boards[currentMove][toY][toX];
4945     do {
4946         pieceSweep -= step;
4947         if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
4948         if((int)pieceSweep == -1) pieceSweep = BlackKing;
4949         if(!step) step = -1;
4950     } while(PieceToChar(pieceSweep) == '.');
4951     boards[currentMove][toY][toX] = pieceSweep;
4952     DrawPosition(FALSE, boards[currentMove]);
4953     boards[currentMove][toY][toX] = piece;
4954 }
4955 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
4956 void
4957 AlphaRank(char *move, int n)
4958 {
4959 //    char *p = move, c; int x, y;
4960
4961     if (appData.debugMode) {
4962         fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
4963     }
4964
4965     if(move[1]=='*' &&
4966        move[2]>='0' && move[2]<='9' &&
4967        move[3]>='a' && move[3]<='x'    ) {
4968         move[1] = '@';
4969         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4970         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4971     } else
4972     if(move[0]>='0' && move[0]<='9' &&
4973        move[1]>='a' && move[1]<='x' &&
4974        move[2]>='0' && move[2]<='9' &&
4975        move[3]>='a' && move[3]<='x'    ) {
4976         /* input move, Shogi -> normal */
4977         move[0] = BOARD_RGHT  -1 - (move[0]-'1') + AAA;
4978         move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
4979         move[2] = BOARD_RGHT  -1 - (move[2]-'1') + AAA;
4980         move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
4981     } else
4982     if(move[1]=='@' &&
4983        move[3]>='0' && move[3]<='9' &&
4984        move[2]>='a' && move[2]<='x'    ) {
4985         move[1] = '*';
4986         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4987         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4988     } else
4989     if(
4990        move[0]>='a' && move[0]<='x' &&
4991        move[3]>='0' && move[3]<='9' &&
4992        move[2]>='a' && move[2]<='x'    ) {
4993          /* output move, normal -> Shogi */
4994         move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
4995         move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
4996         move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
4997         move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
4998         if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
4999     }
5000     if (appData.debugMode) {
5001         fprintf(debugFP, "   out = '%s'\n", move);
5002     }
5003 }
5004
5005 char yy_textstr[8000];
5006
5007 /* Parser for moves from gnuchess, ICS, or user typein box */
5008 Boolean
5009 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5010      char *move;
5011      int moveNum;
5012      ChessMove *moveType;
5013      int *fromX, *fromY, *toX, *toY;
5014      char *promoChar;
5015 {
5016     *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5017
5018     switch (*moveType) {
5019       case WhitePromotion:
5020       case BlackPromotion:
5021       case WhiteNonPromotion:
5022       case BlackNonPromotion:
5023       case NormalMove:
5024       case WhiteCapturesEnPassant:
5025       case BlackCapturesEnPassant:
5026       case WhiteKingSideCastle:
5027       case WhiteQueenSideCastle:
5028       case BlackKingSideCastle:
5029       case BlackQueenSideCastle:
5030       case WhiteKingSideCastleWild:
5031       case WhiteQueenSideCastleWild:
5032       case BlackKingSideCastleWild:
5033       case BlackQueenSideCastleWild:
5034       /* Code added by Tord: */
5035       case WhiteHSideCastleFR:
5036       case WhiteASideCastleFR:
5037       case BlackHSideCastleFR:
5038       case BlackASideCastleFR:
5039       /* End of code added by Tord */
5040       case IllegalMove:         /* bug or odd chess variant */
5041         *fromX = currentMoveString[0] - AAA;
5042         *fromY = currentMoveString[1] - ONE;
5043         *toX = currentMoveString[2] - AAA;
5044         *toY = currentMoveString[3] - ONE;
5045         *promoChar = currentMoveString[4];
5046         if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5047             *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5048     if (appData.debugMode) {
5049         fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5050     }
5051             *fromX = *fromY = *toX = *toY = 0;
5052             return FALSE;
5053         }
5054         if (appData.testLegality) {
5055           return (*moveType != IllegalMove);
5056         } else {
5057           return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5058                         WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5059         }
5060
5061       case WhiteDrop:
5062       case BlackDrop:
5063         *fromX = *moveType == WhiteDrop ?
5064           (int) CharToPiece(ToUpper(currentMoveString[0])) :
5065           (int) CharToPiece(ToLower(currentMoveString[0]));
5066         *fromY = DROP_RANK;
5067         *toX = currentMoveString[2] - AAA;
5068         *toY = currentMoveString[3] - ONE;
5069         *promoChar = NULLCHAR;
5070         return TRUE;
5071
5072       case AmbiguousMove:
5073       case ImpossibleMove:
5074       case EndOfFile:
5075       case ElapsedTime:
5076       case Comment:
5077       case PGNTag:
5078       case NAG:
5079       case WhiteWins:
5080       case BlackWins:
5081       case GameIsDrawn:
5082       default:
5083     if (appData.debugMode) {
5084         fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5085     }
5086         /* bug? */
5087         *fromX = *fromY = *toX = *toY = 0;
5088         *promoChar = NULLCHAR;
5089         return FALSE;
5090     }
5091 }
5092
5093
5094 void
5095 ParsePV(char *pv, Boolean storeComments)
5096 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5097   int fromX, fromY, toX, toY; char promoChar;
5098   ChessMove moveType;
5099   Boolean valid;
5100   int nr = 0;
5101
5102   endPV = forwardMostMove;
5103   do {
5104     while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5105     if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5106     valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5107 if(appData.debugMode){
5108 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);
5109 }
5110     if(!valid && nr == 0 &&
5111        ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5112         nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5113         // Hande case where played move is different from leading PV move
5114         CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5115         CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5116         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5117         if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5118           endPV += 2; // if position different, keep this
5119           moveList[endPV-1][0] = fromX + AAA;
5120           moveList[endPV-1][1] = fromY + ONE;
5121           moveList[endPV-1][2] = toX + AAA;
5122           moveList[endPV-1][3] = toY + ONE;
5123           parseList[endPV-1][0] = NULLCHAR;
5124           safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5125         }
5126       }
5127     pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5128     if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5129     if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5130     if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5131         valid++; // allow comments in PV
5132         continue;
5133     }
5134     nr++;
5135     if(endPV+1 > framePtr) break; // no space, truncate
5136     if(!valid) break;
5137     endPV++;
5138     CopyBoard(boards[endPV], boards[endPV-1]);
5139     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5140     moveList[endPV-1][0] = fromX + AAA;
5141     moveList[endPV-1][1] = fromY + ONE;
5142     moveList[endPV-1][2] = toX + AAA;
5143     moveList[endPV-1][3] = toY + ONE;
5144     moveList[endPV-1][4] = promoChar;
5145     moveList[endPV-1][5] = NULLCHAR;
5146     strncat(moveList[endPV-1], "\n", MOVE_LEN);
5147     if(storeComments)
5148         CoordsToAlgebraic(boards[endPV - 1],
5149                              PosFlags(endPV - 1),
5150                              fromY, fromX, toY, toX, promoChar,
5151                              parseList[endPV - 1]);
5152     else
5153         parseList[endPV-1][0] = NULLCHAR;
5154   } while(valid);
5155   currentMove = endPV;
5156   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5157   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5158                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5159   DrawPosition(TRUE, boards[currentMove]);
5160 }
5161
5162 Boolean
5163 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5164 {
5165         int startPV;
5166         char *p;
5167
5168         if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5169         lastX = x; lastY = y;
5170         while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5171         startPV = index;
5172         while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5173         if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5174         index = startPV;
5175         do{ while(buf[index] && buf[index] != '\n') index++;
5176         } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5177         buf[index] = 0;
5178         ParsePV(buf+startPV, FALSE);
5179         *start = startPV; *end = index-1;
5180         return TRUE;
5181 }
5182
5183 Boolean
5184 LoadPV(int x, int y)
5185 { // called on right mouse click to load PV
5186   int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5187   lastX = x; lastY = y;
5188   ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5189   return TRUE;
5190 }
5191
5192 void
5193 UnLoadPV()
5194 {
5195   if(endPV < 0) return;
5196   endPV = -1;
5197   currentMove = forwardMostMove;
5198   ClearPremoveHighlights();
5199   DrawPosition(TRUE, boards[currentMove]);
5200 }
5201
5202 void
5203 MovePV(int x, int y, int h)
5204 { // step through PV based on mouse coordinates (called on mouse move)
5205   int margin = h>>3, step = 0;
5206
5207   // we must somehow check if right button is still down (might be released off board!)
5208   if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5209   if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5210   if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5211   if(!step) return;
5212   lastX = x; lastY = y;
5213
5214   if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5215   if(endPV < 0) return;
5216   if(y < margin) step = 1; else
5217   if(y > h - margin) step = -1;
5218   if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5219   currentMove += step;
5220   if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5221   SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5222                        moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5223   DrawPosition(FALSE, boards[currentMove]);
5224 }
5225
5226
5227 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5228 // All positions will have equal probability, but the current method will not provide a unique
5229 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5230 #define DARK 1
5231 #define LITE 2
5232 #define ANY 3
5233
5234 int squaresLeft[4];
5235 int piecesLeft[(int)BlackPawn];
5236 int seed, nrOfShuffles;
5237
5238 void GetPositionNumber()
5239 {       // sets global variable seed
5240         int i;
5241
5242         seed = appData.defaultFrcPosition;
5243         if(seed < 0) { // randomize based on time for negative FRC position numbers
5244                 for(i=0; i<50; i++) seed += random();
5245                 seed = random() ^ random() >> 8 ^ random() << 8;
5246                 if(seed<0) seed = -seed;
5247         }
5248 }
5249
5250 int put(Board board, int pieceType, int rank, int n, int shade)
5251 // put the piece on the (n-1)-th empty squares of the given shade
5252 {
5253         int i;
5254
5255         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5256                 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5257                         board[rank][i] = (ChessSquare) pieceType;
5258                         squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5259                         squaresLeft[ANY]--;
5260                         piecesLeft[pieceType]--;
5261                         return i;
5262                 }
5263         }
5264         return -1;
5265 }
5266
5267
5268 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5269 // calculate where the next piece goes, (any empty square), and put it there
5270 {
5271         int i;
5272
5273         i = seed % squaresLeft[shade];
5274         nrOfShuffles *= squaresLeft[shade];
5275         seed /= squaresLeft[shade];
5276         put(board, pieceType, rank, i, shade);
5277 }
5278
5279 void AddTwoPieces(Board board, int pieceType, int rank)
5280 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5281 {
5282         int i, n=squaresLeft[ANY], j=n-1, k;
5283
5284         k = n*(n-1)/2; // nr of possibilities, not counting permutations
5285         i = seed % k;  // pick one
5286         nrOfShuffles *= k;
5287         seed /= k;
5288         while(i >= j) i -= j--;
5289         j = n - 1 - j; i += j;
5290         put(board, pieceType, rank, j, ANY);
5291         put(board, pieceType, rank, i, ANY);
5292 }
5293
5294 void SetUpShuffle(Board board, int number)
5295 {
5296         int i, p, first=1;
5297
5298         GetPositionNumber(); nrOfShuffles = 1;
5299
5300         squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5301         squaresLeft[ANY]  = BOARD_RGHT - BOARD_LEFT;
5302         squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5303
5304         for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5305
5306         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5307             p = (int) board[0][i];
5308             if(p < (int) BlackPawn) piecesLeft[p] ++;
5309             board[0][i] = EmptySquare;
5310         }
5311
5312         if(PosFlags(0) & F_ALL_CASTLE_OK) {
5313             // shuffles restricted to allow normal castling put KRR first
5314             if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5315                 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5316             else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5317                 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5318             if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5319                 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5320             if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5321                 put(board, WhiteRook, 0, 0, ANY);
5322             // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5323         }
5324
5325         if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5326             // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5327             for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5328                 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5329                 while(piecesLeft[p] >= 2) {
5330                     AddOnePiece(board, p, 0, LITE);
5331                     AddOnePiece(board, p, 0, DARK);
5332                 }
5333                 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5334             }
5335
5336         for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5337             // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5338             // but we leave King and Rooks for last, to possibly obey FRC restriction
5339             if(p == (int)WhiteRook) continue;
5340             while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5341             if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY);     // add the odd piece
5342         }
5343
5344         // now everything is placed, except perhaps King (Unicorn) and Rooks
5345
5346         if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5347             // Last King gets castling rights
5348             while(piecesLeft[(int)WhiteUnicorn]) {
5349                 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5350                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5351             }
5352
5353             while(piecesLeft[(int)WhiteKing]) {
5354                 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5355                 initialRights[2]  = initialRights[5]  = board[CASTLING][2] = board[CASTLING][5] = i;
5356             }
5357
5358
5359         } else {
5360             while(piecesLeft[(int)WhiteKing])    AddOnePiece(board, WhiteKing, 0, ANY);
5361             while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5362         }
5363
5364         // Only Rooks can be left; simply place them all
5365         while(piecesLeft[(int)WhiteRook]) {
5366                 i = put(board, WhiteRook, 0, 0, ANY);
5367                 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5368                         if(first) {
5369                                 first=0;
5370                                 initialRights[1]  = initialRights[4]  = board[CASTLING][1] = board[CASTLING][4] = i;
5371                         }
5372                         initialRights[0]  = initialRights[3]  = board[CASTLING][0] = board[CASTLING][3] = i;
5373                 }
5374         }
5375         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5376             board[BOARD_HEIGHT-1][i] =  (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5377         }
5378
5379         if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5380 }
5381
5382 int SetCharTable( char *table, const char * map )
5383 /* [HGM] moved here from winboard.c because of its general usefulness */
5384 /*       Basically a safe strcpy that uses the last character as King */
5385 {
5386     int result = FALSE; int NrPieces;
5387
5388     if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5389                     && NrPieces >= 12 && !(NrPieces&1)) {
5390         int i; /* [HGM] Accept even length from 12 to 34 */
5391
5392         for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5393         for( i=0; i<NrPieces/2-1; i++ ) {
5394             table[i] = map[i];
5395             table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5396         }
5397         table[(int) WhiteKing]  = map[NrPieces/2-1];
5398         table[(int) BlackKing]  = map[NrPieces-1];
5399
5400         result = TRUE;
5401     }
5402
5403     return result;
5404 }
5405
5406 void Prelude(Board board)
5407 {       // [HGM] superchess: random selection of exo-pieces
5408         int i, j, k; ChessSquare p;
5409         static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5410
5411         GetPositionNumber(); // use FRC position number
5412
5413         if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5414             SetCharTable(pieceToChar, appData.pieceToCharTable);
5415             for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5416                 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5417         }
5418
5419         j = seed%4;                 seed /= 4;
5420         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5421         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5422         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5423         j = seed%3 + (seed%3 >= j); seed /= 3;
5424         p = board[0][BOARD_LEFT+j];   board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5425         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5426         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5427         j = seed%3;                 seed /= 3;
5428         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5429         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5430         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5431         j = seed%2 + (seed%2 >= j); seed /= 2;
5432         p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5433         board[k][BOARD_WIDTH-1] = p;  board[k][BOARD_WIDTH-2]++;
5434         board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p;  board[BOARD_HEIGHT-1-k][1]++;
5435         j = seed%4; seed /= 4; put(board, exoPieces[3],    0, j, ANY);
5436         j = seed%3; seed /= 3; put(board, exoPieces[2],   0, j, ANY);
5437         j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5438         put(board, exoPieces[0],    0, 0, ANY);
5439         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5440 }
5441
5442 void
5443 InitPosition(redraw)
5444      int redraw;
5445 {
5446     ChessSquare (* pieces)[BOARD_FILES];
5447     int i, j, pawnRow, overrule,
5448     oldx = gameInfo.boardWidth,
5449     oldy = gameInfo.boardHeight,
5450     oldh = gameInfo.holdingsWidth;
5451     static int oldv;
5452
5453     if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5454
5455     /* [AS] Initialize pv info list [HGM] and game status */
5456     {
5457         for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5458             pvInfoList[i].depth = 0;
5459             boards[i][EP_STATUS] = EP_NONE;
5460             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5461         }
5462
5463         initialRulePlies = 0; /* 50-move counter start */
5464
5465         castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5466         castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5467     }
5468
5469
5470     /* [HGM] logic here is completely changed. In stead of full positions */
5471     /* the initialized data only consist of the two backranks. The switch */
5472     /* selects which one we will use, which is than copied to the Board   */
5473     /* initialPosition, which for the rest is initialized by Pawns and    */
5474     /* empty squares. This initial position is then copied to boards[0],  */
5475     /* possibly after shuffling, so that it remains available.            */
5476
5477     gameInfo.holdingsWidth = 0; /* default board sizes */
5478     gameInfo.boardWidth    = 8;
5479     gameInfo.boardHeight   = 8;
5480     gameInfo.holdingsSize  = 0;
5481     nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5482     for(i=0; i<BOARD_FILES-2; i++)
5483       initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5484     initialPosition[EP_STATUS] = EP_NONE;
5485     SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5486     if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5487          SetCharTable(pieceNickName, appData.pieceNickNames);
5488     else SetCharTable(pieceNickName, "............");
5489     pieces = FIDEArray;
5490
5491     switch (gameInfo.variant) {
5492     case VariantFischeRandom:
5493       shuffleOpenings = TRUE;
5494     default:
5495       break;
5496     case VariantShatranj:
5497       pieces = ShatranjArray;
5498       nrCastlingRights = 0;
5499       SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5500       break;
5501     case VariantMakruk:
5502       pieces = makrukArray;
5503       nrCastlingRights = 0;
5504       startedFromSetupPosition = TRUE;
5505       SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5506       break;
5507     case VariantTwoKings:
5508       pieces = twoKingsArray;
5509       break;
5510     case VariantCapaRandom:
5511       shuffleOpenings = TRUE;
5512     case VariantCapablanca:
5513       pieces = CapablancaArray;
5514       gameInfo.boardWidth = 10;
5515       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5516       break;
5517     case VariantGothic:
5518       pieces = GothicArray;
5519       gameInfo.boardWidth = 10;
5520       SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5521       break;
5522     case VariantSChess:
5523       SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5524       gameInfo.holdingsSize = 7;
5525       break;
5526     case VariantJanus:
5527       pieces = JanusArray;
5528       gameInfo.boardWidth = 10;
5529       SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5530       nrCastlingRights = 6;
5531         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5532         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5533         initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5534         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5535         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5536         initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5537       break;
5538     case VariantFalcon:
5539       pieces = FalconArray;
5540       gameInfo.boardWidth = 10;
5541       SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5542       break;
5543     case VariantXiangqi:
5544       pieces = XiangqiArray;
5545       gameInfo.boardWidth  = 9;
5546       gameInfo.boardHeight = 10;
5547       nrCastlingRights = 0;
5548       SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5549       break;
5550     case VariantShogi:
5551       pieces = ShogiArray;
5552       gameInfo.boardWidth  = 9;
5553       gameInfo.boardHeight = 9;
5554       gameInfo.holdingsSize = 7;
5555       nrCastlingRights = 0;
5556       SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5557       break;
5558     case VariantCourier:
5559       pieces = CourierArray;
5560       gameInfo.boardWidth  = 12;
5561       nrCastlingRights = 0;
5562       SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5563       break;
5564     case VariantKnightmate:
5565       pieces = KnightmateArray;
5566       SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5567       break;
5568     case VariantSpartan:
5569       pieces = SpartanArray;
5570       SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5571       break;
5572     case VariantFairy:
5573       pieces = fairyArray;
5574       SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5575       break;
5576     case VariantGreat:
5577       pieces = GreatArray;
5578       gameInfo.boardWidth = 10;
5579       SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5580       gameInfo.holdingsSize = 8;
5581       break;
5582     case VariantSuper:
5583       pieces = FIDEArray;
5584       SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5585       gameInfo.holdingsSize = 8;
5586       startedFromSetupPosition = TRUE;
5587       break;
5588     case VariantCrazyhouse:
5589     case VariantBughouse:
5590       pieces = FIDEArray;
5591       SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5592       gameInfo.holdingsSize = 5;
5593       break;
5594     case VariantWildCastle:
5595       pieces = FIDEArray;
5596       /* !!?shuffle with kings guaranteed to be on d or e file */
5597       shuffleOpenings = 1;
5598       break;
5599     case VariantNoCastle:
5600       pieces = FIDEArray;
5601       nrCastlingRights = 0;
5602       /* !!?unconstrained back-rank shuffle */
5603       shuffleOpenings = 1;
5604       break;
5605     }
5606
5607     overrule = 0;
5608     if(appData.NrFiles >= 0) {
5609         if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5610         gameInfo.boardWidth = appData.NrFiles;
5611     }
5612     if(appData.NrRanks >= 0) {
5613         gameInfo.boardHeight = appData.NrRanks;
5614     }
5615     if(appData.holdingsSize >= 0) {
5616         i = appData.holdingsSize;
5617         if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5618         gameInfo.holdingsSize = i;
5619     }
5620     if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5621     if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5622         DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5623
5624     pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5625     if(pawnRow < 1) pawnRow = 1;
5626     if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5627
5628     /* User pieceToChar list overrules defaults */
5629     if(appData.pieceToCharTable != NULL)
5630         SetCharTable(pieceToChar, appData.pieceToCharTable);
5631
5632     for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5633
5634         if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5635             s = (ChessSquare) 0; /* account holding counts in guard band */
5636         for( i=0; i<BOARD_HEIGHT; i++ )
5637             initialPosition[i][j] = s;
5638
5639         if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5640         initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5641         initialPosition[pawnRow][j] = WhitePawn;
5642         initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5643         if(gameInfo.variant == VariantXiangqi) {
5644             if(j&1) {
5645                 initialPosition[pawnRow][j] =
5646                 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5647                 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5648                    initialPosition[2][j] = WhiteCannon;
5649                    initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5650                 }
5651             }
5652         }
5653         initialPosition[BOARD_HEIGHT-1][j] =  pieces[1][j-gameInfo.holdingsWidth];
5654     }
5655     if( (gameInfo.variant == VariantShogi) && !overrule ) {
5656
5657             j=BOARD_LEFT+1;
5658             initialPosition[1][j] = WhiteBishop;
5659             initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5660             j=BOARD_RGHT-2;
5661             initialPosition[1][j] = WhiteRook;
5662             initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5663     }
5664
5665     if( nrCastlingRights == -1) {
5666         /* [HGM] Build normal castling rights (must be done after board sizing!) */
5667         /*       This sets default castling rights from none to normal corners   */
5668         /* Variants with other castling rights must set them themselves above    */
5669         nrCastlingRights = 6;
5670
5671         initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5672         initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5673         initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5674         initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5675         initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5676         initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5677      }
5678
5679      if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5680      if(gameInfo.variant == VariantGreat) { // promotion commoners
5681         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5682         initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5683         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5684         initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5685      }
5686      if( gameInfo.variant == VariantSChess ) {
5687       initialPosition[1][0] = BlackMarshall;
5688       initialPosition[2][0] = BlackAngel;
5689       initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5690       initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5691       initialPosition[1][1] = initialPosition[2][1] = 
5692       initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5693      }
5694   if (appData.debugMode) {
5695     fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5696   }
5697     if(shuffleOpenings) {
5698         SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5699         startedFromSetupPosition = TRUE;
5700     }
5701     if(startedFromPositionFile) {
5702       /* [HGM] loadPos: use PositionFile for every new game */
5703       CopyBoard(initialPosition, filePosition);
5704       for(i=0; i<nrCastlingRights; i++)
5705           initialRights[i] = filePosition[CASTLING][i];
5706       startedFromSetupPosition = TRUE;
5707     }
5708
5709     CopyBoard(boards[0], initialPosition);
5710
5711     if(oldx != gameInfo.boardWidth ||
5712        oldy != gameInfo.boardHeight ||
5713        oldv != gameInfo.variant ||
5714        oldh != gameInfo.holdingsWidth
5715                                          )
5716             InitDrawingSizes(-2 ,0);
5717
5718     oldv = gameInfo.variant;
5719     if (redraw)
5720       DrawPosition(TRUE, boards[currentMove]);
5721 }
5722
5723 void
5724 SendBoard(cps, moveNum)
5725      ChessProgramState *cps;
5726      int moveNum;
5727 {
5728     char message[MSG_SIZ];
5729
5730     if (cps->useSetboard) {
5731       char* fen = PositionToFEN(moveNum, cps->fenOverride);
5732       snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5733       SendToProgram(message, cps);
5734       free(fen);
5735
5736     } else {
5737       ChessSquare *bp;
5738       int i, j;
5739       /* Kludge to set black to move, avoiding the troublesome and now
5740        * deprecated "black" command.
5741        */
5742       if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5743         SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5744
5745       SendToProgram("edit\n", cps);
5746       SendToProgram("#\n", cps);
5747       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5748         bp = &boards[moveNum][i][BOARD_LEFT];
5749         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5750           if ((int) *bp < (int) BlackPawn) {
5751             snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5752                     AAA + j, ONE + i);
5753             if(message[0] == '+' || message[0] == '~') {
5754               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5755                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5756                         AAA + j, ONE + i);
5757             }
5758             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5759                 message[1] = BOARD_RGHT   - 1 - j + '1';
5760                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5761             }
5762             SendToProgram(message, cps);
5763           }
5764         }
5765       }
5766
5767       SendToProgram("c\n", cps);
5768       for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5769         bp = &boards[moveNum][i][BOARD_LEFT];
5770         for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5771           if (((int) *bp != (int) EmptySquare)
5772               && ((int) *bp >= (int) BlackPawn)) {
5773             snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5774                     AAA + j, ONE + i);
5775             if(message[0] == '+' || message[0] == '~') {
5776               snprintf(message, MSG_SIZ,"%c%c%c+\n",
5777                         PieceToChar((ChessSquare)(DEMOTED *bp)),
5778                         AAA + j, ONE + i);
5779             }
5780             if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5781                 message[1] = BOARD_RGHT   - 1 - j + '1';
5782                 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5783             }
5784             SendToProgram(message, cps);
5785           }
5786         }
5787       }
5788
5789       SendToProgram(".\n", cps);
5790     }
5791     setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5792 }
5793
5794 ChessSquare
5795 DefaultPromoChoice(int white)
5796 {
5797     ChessSquare result;
5798     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5799         result = WhiteFerz; // no choice
5800     else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5801         result= WhiteKing; // in Suicide Q is the last thing we want
5802     else if(gameInfo.variant == VariantSpartan)
5803         result = white ? WhiteQueen : WhiteAngel;
5804     else result = WhiteQueen;
5805     if(!white) result = WHITE_TO_BLACK result;
5806     return result;
5807 }
5808
5809 static int autoQueen; // [HGM] oneclick
5810
5811 int
5812 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5813 {
5814     /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5815     /* [HGM] add Shogi promotions */
5816     int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5817     ChessSquare piece;
5818     ChessMove moveType;
5819     Boolean premove;
5820
5821     if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5822     if(toX   < BOARD_LEFT || toX   >= BOARD_RGHT) return FALSE; // move into holdings
5823
5824     if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5825       !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5826         return FALSE;
5827
5828     piece = boards[currentMove][fromY][fromX];
5829     if(gameInfo.variant == VariantShogi) {
5830         promotionZoneSize = BOARD_HEIGHT/3;
5831         highestPromotingPiece = (int)WhiteFerz;
5832     } else if(gameInfo.variant == VariantMakruk) {
5833         promotionZoneSize = 3;
5834     }
5835
5836     // Treat Lance as Pawn when it is not representing Amazon
5837     if(gameInfo.variant != VariantSuper) {
5838         if(piece == WhiteLance) piece = WhitePawn; else
5839         if(piece == BlackLance) piece = BlackPawn;
5840     }
5841
5842     // next weed out all moves that do not touch the promotion zone at all
5843     if((int)piece >= BlackPawn) {
5844         if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5845              return FALSE;
5846         highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5847     } else {
5848         if(  toY < BOARD_HEIGHT - promotionZoneSize &&
5849            fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5850     }
5851
5852     if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5853
5854     // weed out mandatory Shogi promotions
5855     if(gameInfo.variant == VariantShogi) {
5856         if(piece >= BlackPawn) {
5857             if(toY == 0 && piece == BlackPawn ||
5858                toY == 0 && piece == BlackQueen ||
5859                toY <= 1 && piece == BlackKnight) {
5860                 *promoChoice = '+';
5861                 return FALSE;
5862             }
5863         } else {
5864             if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
5865                toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
5866                toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
5867                 *promoChoice = '+';
5868                 return FALSE;
5869             }
5870         }
5871     }
5872
5873     // weed out obviously illegal Pawn moves
5874     if(appData.testLegality  && (piece == WhitePawn || piece == BlackPawn) ) {
5875         if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
5876         if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
5877         if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
5878         if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
5879         // note we are not allowed to test for valid (non-)capture, due to premove
5880     }
5881
5882     // we either have a choice what to promote to, or (in Shogi) whether to promote
5883     if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
5884         *promoChoice = PieceToChar(BlackFerz);  // no choice
5885         return FALSE;
5886     }
5887     // no sense asking what we must promote to if it is going to explode...
5888     if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
5889         *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
5890         return FALSE;
5891     }
5892     // give caller the default choice even if we will not make it
5893     *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
5894     if(gameInfo.variant == VariantShogi) *promoChoice = '+';
5895     if(appData.sweepSelect && gameInfo.variant != VariantGreat
5896                            && gameInfo.variant != VariantShogi
5897                            && gameInfo.variant != VariantSuper) return FALSE;
5898     if(autoQueen) return FALSE; // predetermined
5899
5900     // suppress promotion popup on illegal moves that are not premoves
5901     premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
5902               gameMode == IcsPlayingBlack &&  WhiteOnMove(currentMove);
5903     if(appData.testLegality && !premove) {
5904         moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
5905                         fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
5906         if(moveType != WhitePromotion && moveType  != BlackPromotion)
5907             return FALSE;
5908     }
5909
5910     return TRUE;
5911 }
5912
5913 int
5914 InPalace(row, column)
5915      int row, column;
5916 {   /* [HGM] for Xiangqi */
5917     if( (row < 3 || row > BOARD_HEIGHT-4) &&
5918          column < (BOARD_WIDTH + 4)/2 &&
5919          column > (BOARD_WIDTH - 5)/2 ) return TRUE;
5920     return FALSE;
5921 }
5922
5923 int
5924 PieceForSquare (x, y)
5925      int x;
5926      int y;
5927 {
5928   if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
5929      return -1;
5930   else
5931      return boards[currentMove][y][x];
5932 }
5933
5934 int
5935 OKToStartUserMove(x, y)
5936      int x, y;
5937 {
5938     ChessSquare from_piece;
5939     int white_piece;
5940
5941     if (matchMode) return FALSE;
5942     if (gameMode == EditPosition) return TRUE;
5943
5944     if (x >= 0 && y >= 0)
5945       from_piece = boards[currentMove][y][x];
5946     else
5947       from_piece = EmptySquare;
5948
5949     if (from_piece == EmptySquare) return FALSE;
5950
5951     white_piece = (int)from_piece >= (int)WhitePawn &&
5952       (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
5953
5954     switch (gameMode) {
5955       case PlayFromGameFile:
5956       case AnalyzeFile:
5957       case TwoMachinesPlay:
5958       case EndOfGame:
5959         return FALSE;
5960
5961       case IcsObserving:
5962       case IcsIdle:
5963         return FALSE;
5964
5965       case MachinePlaysWhite:
5966       case IcsPlayingBlack:
5967         if (appData.zippyPlay) return FALSE;
5968         if (white_piece) {
5969             DisplayMoveError(_("You are playing Black"));
5970             return FALSE;
5971         }
5972         break;
5973
5974       case MachinePlaysBlack:
5975       case IcsPlayingWhite:
5976         if (appData.zippyPlay) return FALSE;
5977         if (!white_piece) {
5978             DisplayMoveError(_("You are playing White"));
5979             return FALSE;
5980         }
5981         break;
5982
5983       case EditGame:
5984         if (!white_piece && WhiteOnMove(currentMove)) {
5985             DisplayMoveError(_("It is White's turn"));
5986             return FALSE;
5987         }
5988         if (white_piece && !WhiteOnMove(currentMove)) {
5989             DisplayMoveError(_("It is Black's turn"));
5990             return FALSE;
5991         }
5992         if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
5993             /* Editing correspondence game history */
5994             /* Could disallow this or prompt for confirmation */
5995             cmailOldMove = -1;
5996         }
5997         break;
5998
5999       case BeginningOfGame:
6000         if (appData.icsActive) return FALSE;
6001         if (!appData.noChessProgram) {
6002             if (!white_piece) {
6003                 DisplayMoveError(_("You are playing White"));
6004                 return FALSE;
6005             }
6006         }
6007         break;
6008
6009       case Training:
6010         if (!white_piece && WhiteOnMove(currentMove)) {
6011             DisplayMoveError(_("It is White's turn"));
6012             return FALSE;
6013         }
6014         if (white_piece && !WhiteOnMove(currentMove)) {
6015             DisplayMoveError(_("It is Black's turn"));
6016             return FALSE;
6017         }
6018         break;
6019
6020       default:
6021       case IcsExamining:
6022         break;
6023     }
6024     if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6025         && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6026         && gameMode != AnalyzeFile && gameMode != Training) {
6027         DisplayMoveError(_("Displayed position is not current"));
6028         return FALSE;
6029     }
6030     return TRUE;
6031 }
6032
6033 Boolean
6034 OnlyMove(int *x, int *y, Boolean captures) {
6035     DisambiguateClosure cl;
6036     if (appData.zippyPlay) return FALSE;
6037     switch(gameMode) {
6038       case MachinePlaysBlack:
6039       case IcsPlayingWhite:
6040       case BeginningOfGame:
6041         if(!WhiteOnMove(currentMove)) return FALSE;
6042         break;
6043       case MachinePlaysWhite:
6044       case IcsPlayingBlack:
6045         if(WhiteOnMove(currentMove)) return FALSE;
6046         break;
6047       case EditGame:
6048         break;
6049       default:
6050         return FALSE;
6051     }
6052     cl.pieceIn = EmptySquare;
6053     cl.rfIn = *y;
6054     cl.ffIn = *x;
6055     cl.rtIn = -1;
6056     cl.ftIn = -1;
6057     cl.promoCharIn = NULLCHAR;
6058     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6059     if( cl.kind == NormalMove ||
6060         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6061         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6062         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6063       fromX = cl.ff;
6064       fromY = cl.rf;
6065       *x = cl.ft;
6066       *y = cl.rt;
6067       return TRUE;
6068     }
6069     if(cl.kind != ImpossibleMove) return FALSE;
6070     cl.pieceIn = EmptySquare;
6071     cl.rfIn = -1;
6072     cl.ffIn = -1;
6073     cl.rtIn = *y;
6074     cl.ftIn = *x;
6075     cl.promoCharIn = NULLCHAR;
6076     Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6077     if( cl.kind == NormalMove ||
6078         cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6079         cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6080         cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6081       fromX = cl.ff;
6082       fromY = cl.rf;
6083       *x = cl.ft;
6084       *y = cl.rt;
6085       autoQueen = TRUE; // act as if autoQueen on when we click to-square
6086       return TRUE;
6087     }
6088     return FALSE;
6089 }
6090
6091 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6092 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6093 int lastLoadGameUseList = FALSE;
6094 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6095 ChessMove lastLoadGameStart = EndOfFile;
6096
6097 void
6098 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6099      int fromX, fromY, toX, toY;
6100      int promoChar;
6101 {
6102     ChessMove moveType;
6103     ChessSquare pdown, pup;
6104
6105     /* Check if the user is playing in turn.  This is complicated because we
6106        let the user "pick up" a piece before it is his turn.  So the piece he
6107        tried to pick up may have been captured by the time he puts it down!
6108        Therefore we use the color the user is supposed to be playing in this
6109        test, not the color of the piece that is currently on the starting
6110        square---except in EditGame mode, where the user is playing both
6111        sides; fortunately there the capture race can't happen.  (It can
6112        now happen in IcsExamining mode, but that's just too bad.  The user
6113        will get a somewhat confusing message in that case.)
6114        */
6115
6116     switch (gameMode) {
6117       case PlayFromGameFile:
6118       case AnalyzeFile:
6119       case TwoMachinesPlay:
6120       case EndOfGame:
6121       case IcsObserving:
6122       case IcsIdle:
6123         /* We switched into a game mode where moves are not accepted,
6124            perhaps while the mouse button was down. */
6125         return;
6126
6127       case MachinePlaysWhite:
6128         /* User is moving for Black */
6129         if (WhiteOnMove(currentMove)) {
6130             DisplayMoveError(_("It is White's turn"));
6131             return;
6132         }
6133         break;
6134
6135       case MachinePlaysBlack:
6136         /* User is moving for White */
6137         if (!WhiteOnMove(currentMove)) {
6138             DisplayMoveError(_("It is Black's turn"));
6139             return;
6140         }
6141         break;
6142
6143       case EditGame:
6144       case IcsExamining:
6145       case BeginningOfGame:
6146       case AnalyzeMode:
6147       case Training:
6148         if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6149         if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6150             (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6151             /* User is moving for Black */
6152             if (WhiteOnMove(currentMove)) {
6153                 DisplayMoveError(_("It is White's turn"));
6154                 return;
6155             }
6156         } else {
6157             /* User is moving for White */
6158             if (!WhiteOnMove(currentMove)) {
6159                 DisplayMoveError(_("It is Black's turn"));
6160                 return;
6161             }
6162         }
6163         break;
6164
6165       case IcsPlayingBlack:
6166         /* User is moving for Black */
6167         if (WhiteOnMove(currentMove)) {
6168             if (!appData.premove) {
6169                 DisplayMoveError(_("It is White's turn"));
6170             } else if (toX >= 0 && toY >= 0) {
6171                 premoveToX = toX;
6172                 premoveToY = toY;
6173                 premoveFromX = fromX;
6174                 premoveFromY = fromY;
6175                 premovePromoChar = promoChar;
6176                 gotPremove = 1;
6177                 if (appData.debugMode)
6178                     fprintf(debugFP, "Got premove: fromX %d,"
6179                             "fromY %d, toX %d, toY %d\n",
6180                             fromX, fromY, toX, toY);
6181             }
6182             return;
6183         }
6184         break;
6185
6186       case IcsPlayingWhite:
6187         /* User is moving for White */
6188         if (!WhiteOnMove(currentMove)) {
6189             if (!appData.premove) {
6190                 DisplayMoveError(_("It is Black's turn"));
6191             } else if (toX >= 0 && toY >= 0) {
6192                 premoveToX = toX;
6193                 premoveToY = toY;
6194                 premoveFromX = fromX;
6195                 premoveFromY = fromY;
6196                 premovePromoChar = promoChar;
6197                 gotPremove = 1;
6198                 if (appData.debugMode)
6199                     fprintf(debugFP, "Got premove: fromX %d,"
6200                             "fromY %d, toX %d, toY %d\n",
6201                             fromX, fromY, toX, toY);
6202             }
6203             return;
6204         }
6205         break;
6206
6207       default:
6208         break;
6209
6210       case EditPosition:
6211         /* EditPosition, empty square, or different color piece;
6212            click-click move is possible */
6213         if (toX == -2 || toY == -2) {
6214             boards[0][fromY][fromX] = EmptySquare;
6215             DrawPosition(FALSE, boards[currentMove]);
6216             return;
6217         } else if (toX >= 0 && toY >= 0) {
6218             boards[0][toY][toX] = boards[0][fromY][fromX];
6219             if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6220                 if(boards[0][fromY][0] != EmptySquare) {
6221                     if(boards[0][fromY][1]) boards[0][fromY][1]--;
6222                     if(boards[0][fromY][1] == 0)  boards[0][fromY][0] = EmptySquare;
6223                 }
6224             } else
6225             if(fromX == BOARD_RGHT+1) {
6226                 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6227                     if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6228                     if(boards[0][fromY][BOARD_WIDTH-2] == 0)  boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6229                 }
6230             } else
6231             boards[0][fromY][fromX] = EmptySquare;
6232             DrawPosition(FALSE, boards[currentMove]);
6233             return;
6234         }
6235         return;
6236     }
6237
6238     if(toX < 0 || toY < 0) return;
6239     pdown = boards[currentMove][fromY][fromX];
6240     pup = boards[currentMove][toY][toX];
6241
6242     /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6243     if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6244          if( pup != EmptySquare ) return;
6245          moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6246            if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n", 
6247                 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6248            // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6249            if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6250            fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6251            while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++; 
6252          fromY = DROP_RANK;
6253     }
6254
6255     /* [HGM] always test for legality, to get promotion info */
6256     moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6257                                          fromY, fromX, toY, toX, promoChar);
6258     /* [HGM] but possibly ignore an IllegalMove result */
6259     if (appData.testLegality) {
6260         if (moveType == IllegalMove || moveType == ImpossibleMove) {
6261             DisplayMoveError(_("Illegal move"));
6262             return;
6263         }
6264     }
6265
6266     FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6267 }
6268
6269 /* Common tail of UserMoveEvent and DropMenuEvent */
6270 int
6271 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6272      ChessMove moveType;
6273      int fromX, fromY, toX, toY;
6274      /*char*/int promoChar;
6275 {
6276     char *bookHit = 0;
6277
6278     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6279         // [HGM] superchess: suppress promotions to non-available piece
6280         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6281         if(WhiteOnMove(currentMove)) {
6282             if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6283         } else {
6284             if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6285         }
6286     }
6287
6288     /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6289        move type in caller when we know the move is a legal promotion */
6290     if(moveType == NormalMove && promoChar)
6291         moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6292
6293     /* [HGM] <popupFix> The following if has been moved here from
6294        UserMoveEvent(). Because it seemed to belong here (why not allow
6295        piece drops in training games?), and because it can only be
6296        performed after it is known to what we promote. */
6297     if (gameMode == Training) {
6298       /* compare the move played on the board to the next move in the
6299        * game. If they match, display the move and the opponent's response.
6300        * If they don't match, display an error message.
6301        */
6302       int saveAnimate;
6303       Board testBoard;
6304       CopyBoard(testBoard, boards[currentMove]);
6305       ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6306
6307       if (CompareBoards(testBoard, boards[currentMove+1])) {
6308         ForwardInner(currentMove+1);
6309
6310         /* Autoplay the opponent's response.
6311          * if appData.animate was TRUE when Training mode was entered,
6312          * the response will be animated.
6313          */
6314         saveAnimate = appData.animate;
6315         appData.animate = animateTraining;
6316         ForwardInner(currentMove+1);
6317         appData.animate = saveAnimate;
6318
6319         /* check for the end of the game */
6320         if (currentMove >= forwardMostMove) {
6321           gameMode = PlayFromGameFile;
6322           ModeHighlight();
6323           SetTrainingModeOff();
6324           DisplayInformation(_("End of game"));
6325         }
6326       } else {
6327         DisplayError(_("Incorrect move"), 0);
6328       }
6329       return 1;
6330     }
6331
6332   /* Ok, now we know that the move is good, so we can kill
6333      the previous line in Analysis Mode */
6334   if ((gameMode == AnalyzeMode || gameMode == EditGame)
6335                                 && currentMove < forwardMostMove) {
6336     if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6337     else forwardMostMove = currentMove;
6338   }
6339
6340   /* If we need the chess program but it's dead, restart it */
6341   ResurrectChessProgram();
6342
6343   /* A user move restarts a paused game*/
6344   if (pausing)
6345     PauseEvent();
6346
6347   thinkOutput[0] = NULLCHAR;
6348
6349   MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6350
6351   if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6352     ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6353     return 1;
6354   }
6355
6356   if (gameMode == BeginningOfGame) {
6357     if (appData.noChessProgram) {
6358       gameMode = EditGame;
6359       SetGameInfo();
6360     } else {
6361       char buf[MSG_SIZ];
6362       gameMode = MachinePlaysBlack;
6363       StartClocks();
6364       SetGameInfo();
6365       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6366       DisplayTitle(buf);
6367       if (first.sendName) {
6368         snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6369         SendToProgram(buf, &first);
6370       }
6371       StartClocks();
6372     }
6373     ModeHighlight();
6374   }
6375
6376   /* Relay move to ICS or chess engine */
6377   if (appData.icsActive) {
6378     if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6379         gameMode == IcsExamining) {
6380       if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6381         SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6382         SendToICS("draw ");
6383         SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6384       }
6385       // also send plain move, in case ICS does not understand atomic claims
6386       SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6387       ics_user_moved = 1;
6388     }
6389   } else {
6390     if (first.sendTime && (gameMode == BeginningOfGame ||
6391                            gameMode == MachinePlaysWhite ||
6392                            gameMode == MachinePlaysBlack)) {
6393       SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6394     }
6395     if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6396          // [HGM] book: if program might be playing, let it use book
6397         bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6398         first.maybeThinking = TRUE;
6399     } else SendMoveToProgram(forwardMostMove-1, &first);
6400     if (currentMove == cmailOldMove + 1) {
6401       cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6402     }
6403   }
6404
6405   ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6406
6407   switch (gameMode) {
6408   case EditGame:
6409     if(appData.testLegality)
6410     switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6411     case MT_NONE:
6412     case MT_CHECK:
6413       break;
6414     case MT_CHECKMATE:
6415     case MT_STAINMATE:
6416       if (WhiteOnMove(currentMove)) {
6417         GameEnds(BlackWins, "Black mates", GE_PLAYER);
6418       } else {
6419         GameEnds(WhiteWins, "White mates", GE_PLAYER);
6420       }
6421       break;
6422     case MT_STALEMATE:
6423       GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6424       break;
6425     }
6426     break;
6427
6428   case MachinePlaysBlack:
6429   case MachinePlaysWhite:
6430     /* disable certain menu options while machine is thinking */
6431     SetMachineThinkingEnables();
6432     break;
6433
6434   default:
6435     break;
6436   }
6437
6438   userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6439   promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6440
6441   if(bookHit) { // [HGM] book: simulate book reply
6442         static char bookMove[MSG_SIZ]; // a bit generous?
6443
6444         programStats.nodes = programStats.depth = programStats.time =
6445         programStats.score = programStats.got_only_move = 0;
6446         sprintf(programStats.movelist, "%s (xbook)", bookHit);
6447
6448         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6449         strcat(bookMove, bookHit);
6450         HandleMachineMove(bookMove, &first);
6451   }
6452   return 1;
6453 }
6454
6455 void
6456 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6457      Board board;
6458      int flags;
6459      ChessMove kind;
6460      int rf, ff, rt, ft;
6461      VOIDSTAR closure;
6462 {
6463     typedef char Markers[BOARD_RANKS][BOARD_FILES];
6464     Markers *m = (Markers *) closure;
6465     if(rf == fromY && ff == fromX)
6466         (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6467                          || kind == WhiteCapturesEnPassant
6468                          || kind == BlackCapturesEnPassant);
6469     else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6470 }
6471
6472 void
6473 MarkTargetSquares(int clear)
6474 {
6475   int x, y;
6476   if(!appData.markers || !appData.highlightDragging ||
6477      !appData.testLegality || gameMode == EditPosition) return;
6478   if(clear) {
6479     for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6480   } else {
6481     int capt = 0;
6482     GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6483     if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6484       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6485       if(capt)
6486       for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6487     }
6488   }
6489   DrawPosition(TRUE, NULL);
6490 }
6491
6492 int
6493 Explode(Board board, int fromX, int fromY, int toX, int toY)
6494 {
6495     if(gameInfo.variant == VariantAtomic &&
6496        (board[toY][toX] != EmptySquare ||                     // capture?
6497         toX != fromX && (board[fromY][fromX] == WhitePawn ||  // e.p. ?
6498                          board[fromY][fromX] == BlackPawn   )
6499       )) {
6500         AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6501         return TRUE;
6502     }
6503     return FALSE;
6504 }
6505
6506 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6507
6508 int CanPromote(ChessSquare piece, int y)
6509 {
6510         if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6511         // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6512         if(gameInfo.variant == VariantShogi    || gameInfo.variant == VariantXiangqi ||
6513            gameInfo.variant == VariantSuper    || gameInfo.variant == VariantGreat   ||
6514            gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6515                                                   gameInfo.variant == VariantMakruk) return FALSE;
6516         return (piece == BlackPawn && y == 1 ||
6517                 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6518                 piece == BlackLance && y == 1 ||
6519                 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6520 }
6521
6522 void LeftClick(ClickType clickType, int xPix, int yPix)
6523 {
6524     int x, y;
6525     Boolean saveAnimate;
6526     static int second = 0, promotionChoice = 0, clearFlag = 0;
6527     char promoChoice = NULLCHAR;
6528     ChessSquare piece;
6529
6530     if(appData.seekGraph && appData.icsActive && loggedOn &&
6531         (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6532         SeekGraphClick(clickType, xPix, yPix, 0);
6533         return;
6534     }
6535
6536     if (clickType == Press) ErrorPopDown();
6537     MarkTargetSquares(1);
6538
6539     x = EventToSquare(xPix, BOARD_WIDTH);
6540     y = EventToSquare(yPix, BOARD_HEIGHT);
6541     if (!flipView && y >= 0) {
6542         y = BOARD_HEIGHT - 1 - y;
6543     }
6544     if (flipView && x >= 0) {
6545         x = BOARD_WIDTH - 1 - x;
6546     }
6547
6548     if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6549         defaultPromoChoice = promoSweep;
6550         promoSweep = EmptySquare;   // terminate sweep
6551         promoDefaultAltered = TRUE;
6552         if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6553     }
6554
6555     if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6556         if(clickType == Release) return; // ignore upclick of click-click destination
6557         promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6558         if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6559         if(gameInfo.holdingsWidth &&
6560                 (WhiteOnMove(currentMove)
6561                         ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6562                         : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6563             // click in right holdings, for determining promotion piece
6564             ChessSquare p = boards[currentMove][y][x];
6565             if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6566             if(p != EmptySquare) {
6567                 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6568                 fromX = fromY = -1;
6569                 return;
6570             }
6571         }
6572         DrawPosition(FALSE, boards[currentMove]);
6573         return;
6574     }
6575
6576     /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6577     if(clickType == Press
6578             && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6579               || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6580               || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6581         return;
6582
6583     if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6584         fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6585
6586     if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6587         int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6588                     gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6589         defaultPromoChoice = DefaultPromoChoice(side);
6590     }
6591
6592     autoQueen = appData.alwaysPromoteToQueen;
6593
6594     if (fromX == -1) {
6595       int originalY = y;
6596       gatingPiece = EmptySquare;
6597       if (clickType != Press) {
6598         if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6599             DragPieceEnd(xPix, yPix); dragging = 0;
6600             DrawPosition(FALSE, NULL);
6601         }
6602         return;
6603       }
6604       fromX = x; fromY = y;
6605       if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6606          // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6607          appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6608             /* First square */
6609             if (OKToStartUserMove(fromX, fromY)) {
6610                 second = 0;
6611                 MarkTargetSquares(0);
6612                 DragPieceBegin(xPix, yPix); dragging = 1;
6613                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6614                     promoSweep = defaultPromoChoice;
6615                     selectFlag = 0; lastX = xPix; lastY = yPix;
6616                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6617                     DisplayMessage("", _("Pull pawn backwards to under-promote"));
6618                 }
6619                 if (appData.highlightDragging) {
6620                     SetHighlights(fromX, fromY, -1, -1);
6621                 }
6622             } else fromX = fromY = -1;
6623             return;
6624         }
6625     }
6626
6627     /* fromX != -1 */
6628     if (clickType == Press && gameMode != EditPosition) {
6629         ChessSquare fromP;
6630         ChessSquare toP;
6631         int frc;
6632
6633         // ignore off-board to clicks
6634         if(y < 0 || x < 0) return;
6635
6636         /* Check if clicking again on the same color piece */
6637         fromP = boards[currentMove][fromY][fromX];
6638         toP = boards[currentMove][y][x];
6639         frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6640         if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6641              WhitePawn <= toP && toP <= WhiteKing &&
6642              !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6643              !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6644             (BlackPawn <= fromP && fromP <= BlackKing &&
6645              BlackPawn <= toP && toP <= BlackKing &&
6646              !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6647              !(fromP == BlackKing && toP == BlackRook && frc))) {
6648             /* Clicked again on same color piece -- changed his mind */
6649             second = (x == fromX && y == fromY);
6650             promoDefaultAltered = FALSE;
6651            if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6652             if (appData.highlightDragging) {
6653                 SetHighlights(x, y, -1, -1);
6654             } else {
6655                 ClearHighlights();
6656             }
6657             if (OKToStartUserMove(x, y)) {
6658                 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6659                   (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6660                y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6661                  gatingPiece = boards[currentMove][fromY][fromX];
6662                 else gatingPiece = EmptySquare;
6663                 fromX = x;
6664                 fromY = y; dragging = 1;
6665                 MarkTargetSquares(0);
6666                 DragPieceBegin(xPix, yPix);
6667                 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6668                     promoSweep = defaultPromoChoice;
6669                     selectFlag = 0; lastX = xPix; lastY = yPix;
6670                     Sweep(0); // Pawn that is going to promote: preview promotion piece
6671                 }
6672             }
6673            }
6674            if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6675            second = FALSE; 
6676         }
6677         // ignore clicks on holdings
6678         if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6679     }
6680
6681     if (clickType == Release && x == fromX && y == fromY) {
6682         DragPieceEnd(xPix, yPix); dragging = 0;
6683         if(clearFlag) {
6684             // a deferred attempt to click-click move an empty square on top of a piece
6685             boards[currentMove][y][x] = EmptySquare;
6686             ClearHighlights();
6687             DrawPosition(FALSE, boards[currentMove]);
6688             fromX = fromY = -1; clearFlag = 0;
6689             return;
6690         }
6691         if (appData.animateDragging) {
6692             /* Undo animation damage if any */
6693             DrawPosition(FALSE, NULL);
6694         }
6695         if (second) {
6696             /* Second up/down in same square; just abort move */
6697             second = 0;
6698             fromX = fromY = -1;
6699             gatingPiece = EmptySquare;
6700             ClearHighlights();
6701             gotPremove = 0;
6702             ClearPremoveHighlights();
6703         } else {
6704             /* First upclick in same square; start click-click mode */
6705             SetHighlights(x, y, -1, -1);
6706         }
6707         return;
6708     }
6709
6710     clearFlag = 0;
6711
6712     /* we now have a different from- and (possibly off-board) to-square */
6713     /* Completed move */
6714     toX = x;
6715     toY = y;
6716     saveAnimate = appData.animate;
6717     if (clickType == Press) {
6718         if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6719             // must be Edit Position mode with empty-square selected
6720             fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6721             if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6722             return;
6723         }
6724         /* Finish clickclick move */
6725         if (appData.animate || appData.highlightLastMove) {
6726             SetHighlights(fromX, fromY, toX, toY);
6727         } else {
6728             ClearHighlights();
6729         }
6730     } else {
6731         /* Finish drag move */
6732         if (appData.highlightLastMove) {
6733             SetHighlights(fromX, fromY, toX, toY);
6734         } else {
6735             ClearHighlights();
6736         }
6737         DragPieceEnd(xPix, yPix); dragging = 0;
6738         /* Don't animate move and drag both */
6739         appData.animate = FALSE;
6740     }
6741
6742     // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6743     if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6744         ChessSquare piece = boards[currentMove][fromY][fromX];
6745         if(gameMode == EditPosition && piece != EmptySquare &&
6746            fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6747             int n;
6748
6749             if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6750                 n = PieceToNumber(piece - (int)BlackPawn);
6751                 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6752                 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6753                 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6754             } else
6755             if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6756                 n = PieceToNumber(piece);
6757                 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6758                 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6759                 boards[currentMove][n][BOARD_WIDTH-2]++;
6760             }
6761             boards[currentMove][fromY][fromX] = EmptySquare;
6762         }
6763         ClearHighlights();
6764         fromX = fromY = -1;
6765         DrawPosition(TRUE, boards[currentMove]);
6766         return;
6767     }
6768
6769     // off-board moves should not be highlighted
6770     if(x < 0 || y < 0) ClearHighlights();
6771
6772     if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6773
6774     if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6775         SetHighlights(fromX, fromY, toX, toY);
6776         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6777             // [HGM] super: promotion to captured piece selected from holdings
6778             ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6779             promotionChoice = TRUE;
6780             // kludge follows to temporarily execute move on display, without promoting yet
6781             boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6782             boards[currentMove][toY][toX] = p;
6783             DrawPosition(FALSE, boards[currentMove]);
6784             boards[currentMove][fromY][fromX] = p; // take back, but display stays
6785             boards[currentMove][toY][toX] = q;
6786             DisplayMessage("Click in holdings to choose piece", "");
6787             return;
6788         }
6789         PromotionPopUp();
6790     } else {
6791         int oldMove = currentMove;
6792         UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6793         if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6794         if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6795         if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6796            Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6797             DrawPosition(TRUE, boards[currentMove]);
6798         fromX = fromY = -1;
6799     }
6800     appData.animate = saveAnimate;
6801     if (appData.animate || appData.animateDragging) {
6802         /* Undo animation damage if needed */
6803         DrawPosition(FALSE, NULL);
6804     }
6805 }
6806
6807 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6808 {   // front-end-free part taken out of PieceMenuPopup
6809     int whichMenu; int xSqr, ySqr;
6810
6811     if(seekGraphUp) { // [HGM] seekgraph
6812         if(action == Press)   SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6813         if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6814         return -2;
6815     }
6816
6817     if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6818          && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6819         if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6820         if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6821         if(action == Press)   {
6822             originalFlip = flipView;
6823             flipView = !flipView; // temporarily flip board to see game from partners perspective
6824             DrawPosition(TRUE, partnerBoard);
6825             DisplayMessage(partnerStatus, "");
6826             partnerUp = TRUE;
6827         } else if(action == Release) {
6828             flipView = originalFlip;
6829             DrawPosition(TRUE, boards[currentMove]);
6830             partnerUp = FALSE;
6831         }
6832         return -2;
6833     }
6834
6835     xSqr = EventToSquare(x, BOARD_WIDTH);
6836     ySqr = EventToSquare(y, BOARD_HEIGHT);
6837     if (action == Release) {
6838         if(pieceSweep != EmptySquare) {
6839             EditPositionMenuEvent(pieceSweep, toX, toY);
6840             pieceSweep = EmptySquare;
6841         } else UnLoadPV(); // [HGM] pv
6842     }
6843     if (action != Press) return -2; // return code to be ignored
6844     switch (gameMode) {
6845       case IcsExamining:
6846         if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
6847       case EditPosition:
6848         if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
6849         if (xSqr < 0 || ySqr < 0) return -1;
6850         if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6851         pieceSweep = shiftKey ? BlackPawn : WhitePawn;  // [HGM] sweep: prepare selecting piece by mouse sweep
6852         toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6853         if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6854         NextPiece(0);
6855         return -2;\r
6856       case IcsObserving:
6857         if(!appData.icsEngineAnalyze) return -1;
6858       case IcsPlayingWhite:
6859       case IcsPlayingBlack:
6860         if(!appData.zippyPlay) goto noZip;
6861       case AnalyzeMode:
6862       case AnalyzeFile:
6863       case MachinePlaysWhite:
6864       case MachinePlaysBlack:
6865       case TwoMachinesPlay: // [HGM] pv: use for showing PV
6866         if (!appData.dropMenu) {
6867           LoadPV(x, y);
6868           return 2; // flag front-end to grab mouse events
6869         }
6870         if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
6871            gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
6872       case EditGame:
6873       noZip:
6874         if (xSqr < 0 || ySqr < 0) return -1;
6875         if (!appData.dropMenu || appData.testLegality &&
6876             gameInfo.variant != VariantBughouse &&
6877             gameInfo.variant != VariantCrazyhouse) return -1;
6878         whichMenu = 1; // drop menu
6879         break;
6880       default:
6881         return -1;
6882     }
6883
6884     if (((*fromX = xSqr) < 0) ||
6885         ((*fromY = ySqr) < 0)) {
6886         *fromX = *fromY = -1;
6887         return -1;
6888     }
6889     if (flipView)
6890       *fromX = BOARD_WIDTH - 1 - *fromX;
6891     else
6892       *fromY = BOARD_HEIGHT - 1 - *fromY;
6893
6894     return whichMenu;
6895 }
6896
6897 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
6898 {
6899 //    char * hint = lastHint;
6900     FrontEndProgramStats stats;
6901
6902     stats.which = cps == &first ? 0 : 1;
6903     stats.depth = cpstats->depth;
6904     stats.nodes = cpstats->nodes;
6905     stats.score = cpstats->score;
6906     stats.time = cpstats->time;
6907     stats.pv = cpstats->movelist;
6908     stats.hint = lastHint;
6909     stats.an_move_index = 0;
6910     stats.an_move_count = 0;
6911
6912     if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
6913         stats.hint = cpstats->move_name;
6914         stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
6915         stats.an_move_count = cpstats->nr_moves;
6916     }
6917
6918     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
6919
6920     SetProgramStats( &stats );
6921 }
6922
6923 void
6924 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
6925 {       // count all piece types
6926         int p, f, r;
6927         *nB = *nW = *wStale = *bStale = *bishopColor = 0;
6928         for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
6929         for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
6930                 p = board[r][f];
6931                 pCnt[p]++;
6932                 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
6933                 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
6934                 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
6935                 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
6936                    p == BlackBishop || p == BlackFerz || p == BlackAlfil   )
6937                         *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
6938         }
6939 }
6940
6941 int
6942 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
6943 {
6944         int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
6945         int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
6946
6947         nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
6948         if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
6949         if(myPawns == 2 && nMine == 3) // KPP
6950             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
6951         if(myPawns == 1 && nMine == 2) // KP
6952             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]  + pCnt[BlackPawn-side] >= 1;
6953         if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
6954             return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
6955         if(myPawns) return FALSE;
6956         if(pCnt[WhiteRook+side])
6957             return pCnt[BlackRook-side] ||
6958                    pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
6959                    pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
6960                    pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
6961         if(pCnt[WhiteCannon+side]) {
6962             if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
6963             return majorDefense || pCnt[BlackAlfil-side] >= 2;
6964         }
6965         if(pCnt[WhiteKnight+side])
6966             return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
6967         return FALSE;
6968 }
6969
6970 int
6971 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
6972 {
6973         VariantClass v = gameInfo.variant;
6974
6975         if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
6976         if(v == VariantShatranj) return TRUE; // always winnable through baring
6977         if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
6978         if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
6979
6980         if(v == VariantXiangqi) {
6981                 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
6982
6983                 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
6984                 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
6985                 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
6986                 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
6987                 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
6988                 if(stale) // we have at least one last-rank P plus perhaps C
6989                     return majors // KPKX
6990                         || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
6991                 else // KCA*E*
6992                     return pCnt[WhiteFerz+side] // KCAK
6993                         || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
6994                         || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
6995                 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
6996
6997         } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
6998                 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
6999
7000                 if(nMine == 1) return FALSE; // bare King
7001                 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
7002                 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7003                 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7004                 // by now we have King + 1 piece (or multiple Bishops on the same color)
7005                 if(pCnt[WhiteKnight+side])
7006                         return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7007                                 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7008                              || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7009                 if(nBishops)
7010                         return (pCnt[BlackKnight-side]); // KBKN, KFKN
7011                 if(pCnt[WhiteAlfil+side])
7012                         return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7013                 if(pCnt[WhiteWazir+side])
7014                         return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7015         }
7016
7017         return TRUE;
7018 }
7019
7020 int
7021 Adjudicate(ChessProgramState *cps)
7022 {       // [HGM] some adjudications useful with buggy engines
7023         // [HGM] adjudicate: made into separate routine, which now can be called after every move
7024         //       In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7025         //       Actually ending the game is now based on the additional internal condition canAdjudicate.
7026         //       Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7027         int k, count = 0; static int bare = 1;
7028         ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7029         Boolean canAdjudicate = !appData.icsActive;
7030
7031         // most tests only when we understand the game, i.e. legality-checking on
7032             if( appData.testLegality )
7033             {   /* [HGM] Some more adjudications for obstinate engines */
7034                 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7035                 static int moveCount = 6;
7036                 ChessMove result;
7037                 char *reason = NULL;
7038
7039                 /* Count what is on board. */
7040                 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7041
7042                 /* Some material-based adjudications that have to be made before stalemate test */
7043                 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7044                     // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7045                      boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7046                      if(canAdjudicate && appData.checkMates) {
7047                          if(engineOpponent)
7048                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7049                          GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7050                                                         "Xboard adjudication: King destroyed", GE_XBOARD );
7051                          return 1;
7052                      }
7053                 }
7054
7055                 /* Bare King in Shatranj (loses) or Losers (wins) */
7056                 if( nrW == 1 || nrB == 1) {
7057                   if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7058                      boards[forwardMostMove][EP_STATUS] = EP_WINS;  // mark as win, so it becomes claimable
7059                      if(canAdjudicate && appData.checkMates) {
7060                          if(engineOpponent)
7061                            SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7062                          GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7063                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7064                          return 1;
7065                      }
7066                   } else
7067                   if( gameInfo.variant == VariantShatranj && --bare < 0)
7068                   {    /* bare King */
7069                         boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7070                         if(canAdjudicate && appData.checkMates) {
7071                             /* but only adjudicate if adjudication enabled */
7072                             if(engineOpponent)
7073                               SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7074                             GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7075                                                         "Xboard adjudication: Bare king", GE_XBOARD );
7076                             return 1;
7077                         }
7078                   }
7079                 } else bare = 1;
7080
7081
7082             // don't wait for engine to announce game end if we can judge ourselves
7083             switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7084               case MT_CHECK:
7085                 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7086                     int i, checkCnt = 0;    // (should really be done by making nr of checks part of game state)
7087                     for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7088                         if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7089                             checkCnt++;
7090                         if(checkCnt >= 2) {
7091                             reason = "Xboard adjudication: 3rd check";
7092                             boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7093                             break;
7094                         }
7095                     }
7096                 }
7097               case MT_NONE:
7098               default:
7099                 break;
7100               case MT_STALEMATE:
7101               case MT_STAINMATE:
7102                 reason = "Xboard adjudication: Stalemate";
7103                 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7104                     boards[forwardMostMove][EP_STATUS] = EP_STALEMATE;   // default result for stalemate is draw
7105                     if(gameInfo.variant == VariantLosers  || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7106                         boards[forwardMostMove][EP_STATUS] = EP_WINS;    // in these variants stalemated is always a win
7107                     else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7108                         boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7109                                                    ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7110                                                                         EP_CHECKMATE : EP_WINS);
7111                     else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7112                         boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7113                 }
7114                 break;
7115               case MT_CHECKMATE:
7116                 reason = "Xboard adjudication: Checkmate";
7117                 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7118                 break;
7119             }
7120
7121                 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7122                     case EP_STALEMATE:
7123                         result = GameIsDrawn; break;
7124                     case EP_CHECKMATE:
7125                         result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7126                     case EP_WINS:
7127                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7128                     default:
7129                         result = EndOfFile;
7130                 }
7131                 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7132                     if(engineOpponent)
7133                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7134                     GameEnds( result, reason, GE_XBOARD );
7135                     return 1;
7136                 }
7137
7138                 /* Next absolutely insufficient mating material. */
7139                 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7140                    !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7141                 {    /* includes KBK, KNK, KK of KBKB with like Bishops */
7142
7143                      /* always flag draws, for judging claims */
7144                      boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7145
7146                      if(canAdjudicate && appData.materialDraws) {
7147                          /* but only adjudicate them if adjudication enabled */
7148                          if(engineOpponent) {
7149                            SendToProgram("force\n", engineOpponent); // suppress reply
7150                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7151                          }
7152                          GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7153                          return 1;
7154                      }
7155                 }
7156
7157                 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7158                 if(gameInfo.variant == VariantXiangqi ?
7159                        SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7160                  : nrW + nrB == 4 &&
7161                    (   nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7162                    || nr[WhiteQueen] && nr[BlackQueen]==1     /* KQKQ */
7163                    || nr[WhiteKnight]==2 || nr[BlackKnight]==2     /* KNNK */
7164                    || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7165                    ) ) {
7166                      if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7167                      {    /* if the first 3 moves do not show a tactical win, declare draw */
7168                           if(engineOpponent) {
7169                             SendToProgram("force\n", engineOpponent); // suppress reply
7170                             SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7171                           }
7172                           GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7173                           return 1;
7174                      }
7175                 } else moveCount = 6;
7176             }
7177         if (appData.debugMode) { int i;
7178             fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7179                     forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7180                     appData.drawRepeats);
7181             for( i=forwardMostMove; i>=backwardMostMove; i-- )
7182               fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7183
7184         }
7185
7186         // Repetition draws and 50-move rule can be applied independently of legality testing
7187
7188                 /* Check for rep-draws */
7189                 count = 0;
7190                 for(k = forwardMostMove-2;
7191                     k>=backwardMostMove && k>=forwardMostMove-100 &&
7192                         (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7193                         (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7194                     k-=2)
7195                 {   int rights=0;
7196                     if(CompareBoards(boards[k], boards[forwardMostMove])) {
7197                         /* compare castling rights */
7198                         if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7199                              (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7200                                 rights++; /* King lost rights, while rook still had them */
7201                         if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7202                             if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7203                                 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7204                                    rights++; /* but at least one rook lost them */
7205                         }
7206                         if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7207                              (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7208                                 rights++;
7209                         if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7210                             if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7211                                 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7212                                    rights++;
7213                         }
7214                         if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7215                             && appData.drawRepeats > 1) {
7216                              /* adjudicate after user-specified nr of repeats */
7217                              int result = GameIsDrawn;
7218                              char *details = "XBoard adjudication: repetition draw";
7219                              if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7220                                 // [HGM] xiangqi: check for forbidden perpetuals
7221                                 int m, ourPerpetual = 1, hisPerpetual = 1;
7222                                 for(m=forwardMostMove; m>k; m-=2) {
7223                                     if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7224                                         ourPerpetual = 0; // the current mover did not always check
7225                                     if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7226                                         hisPerpetual = 0; // the opponent did not always check
7227                                 }
7228                                 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7229                                                                         ourPerpetual, hisPerpetual);
7230                                 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7231                                     result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7232                                     details = "Xboard adjudication: perpetual checking";
7233                                 } else
7234                                 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7235                                     break; // (or we would have caught him before). Abort repetition-checking loop.
7236                                 } else
7237                                 // Now check for perpetual chases
7238                                 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7239                                     hisPerpetual = PerpetualChase(k, forwardMostMove);
7240                                     ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7241                                     if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7242                                         result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7243                                         details = "Xboard adjudication: perpetual chasing";
7244                                     } else
7245                                     if(hisPerpetual && !ourPerpetual)   // he is chasing us, but did not repeat yet
7246                                         break; // Abort repetition-checking loop.
7247                                 }
7248                                 // if neither of us is checking or chasing all the time, or both are, it is draw
7249                              }
7250                              if(engineOpponent) {
7251                                SendToProgram("force\n", engineOpponent); // suppress reply
7252                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7253                              }
7254                              GameEnds( result, details, GE_XBOARD );
7255                              return 1;
7256                         }
7257                         if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7258                              boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7259                     }
7260                 }
7261
7262                 /* Now we test for 50-move draws. Determine ply count */
7263                 count = forwardMostMove;
7264                 /* look for last irreversble move */
7265                 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7266                     count--;
7267                 /* if we hit starting position, add initial plies */
7268                 if( count == backwardMostMove )
7269                     count -= initialRulePlies;
7270                 count = forwardMostMove - count;
7271                 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7272                         // adjust reversible move counter for checks in Xiangqi
7273                         int i = forwardMostMove - count, inCheck = 0, lastCheck;
7274                         if(i < backwardMostMove) i = backwardMostMove;
7275                         while(i <= forwardMostMove) {
7276                                 lastCheck = inCheck; // check evasion does not count
7277                                 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7278                                 if(inCheck || lastCheck) count--; // check does not count
7279                                 i++;
7280                         }
7281                 }
7282                 if( count >= 100)
7283                          boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7284                          /* this is used to judge if draw claims are legal */
7285                 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7286                          if(engineOpponent) {
7287                            SendToProgram("force\n", engineOpponent); // suppress reply
7288                            SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7289                          }
7290                          GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7291                          return 1;
7292                 }
7293
7294                 /* if draw offer is pending, treat it as a draw claim
7295                  * when draw condition present, to allow engines a way to
7296                  * claim draws before making their move to avoid a race
7297                  * condition occurring after their move
7298                  */
7299                 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7300                          char *p = NULL;
7301                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7302                              p = "Draw claim: 50-move rule";
7303                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7304                              p = "Draw claim: 3-fold repetition";
7305                          if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7306                              p = "Draw claim: insufficient mating material";
7307                          if( p != NULL && canAdjudicate) {
7308                              if(engineOpponent) {
7309                                SendToProgram("force\n", engineOpponent); // suppress reply
7310                                SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7311                              }
7312                              GameEnds( GameIsDrawn, p, GE_XBOARD );
7313                              return 1;
7314                          }
7315                 }
7316
7317                 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7318                     if(engineOpponent) {
7319                       SendToProgram("force\n", engineOpponent); // suppress reply
7320                       SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7321                     }
7322                     GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7323                     return 1;
7324                 }
7325         return 0;
7326 }
7327
7328 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7329 {   // [HGM] book: this routine intercepts moves to simulate book replies
7330     char *bookHit = NULL;
7331
7332     //first determine if the incoming move brings opponent into his book
7333     if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7334         bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7335     if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7336     if(bookHit != NULL && !cps->bookSuspend) {
7337         // make sure opponent is not going to reply after receiving move to book position
7338         SendToProgram("force\n", cps);
7339         cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7340     }
7341     if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7342     // now arrange restart after book miss
7343     if(bookHit) {
7344         // after a book hit we never send 'go', and the code after the call to this routine
7345         // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7346         char buf[MSG_SIZ];
7347         snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7348         SendToProgram(buf, cps);
7349         if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7350     } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7351         SendToProgram("go\n", cps);
7352         cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7353     } else { // 'go' might be sent based on 'firstMove' after this routine returns
7354         if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7355             SendToProgram("go\n", cps);
7356         cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7357     }
7358     return bookHit; // notify caller of hit, so it can take action to send move to opponent
7359 }
7360
7361 char *savedMessage;
7362 ChessProgramState *savedState;
7363 void DeferredBookMove(void)
7364 {
7365         if(savedState->lastPing != savedState->lastPong)
7366                     ScheduleDelayedEvent(DeferredBookMove, 10);
7367         else
7368         HandleMachineMove(savedMessage, savedState);
7369 }
7370
7371 void
7372 HandleMachineMove(message, cps)
7373      char *message;
7374      ChessProgramState *cps;
7375 {
7376     char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7377     char realname[MSG_SIZ];
7378     int fromX, fromY, toX, toY;
7379     ChessMove moveType;
7380     char promoChar;
7381     char *p;
7382     int machineWhite;
7383     char *bookHit;
7384
7385     cps->userError = 0;
7386
7387 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7388     /*
7389      * Kludge to ignore BEL characters
7390      */
7391     while (*message == '\007') message++;
7392
7393     /*
7394      * [HGM] engine debug message: ignore lines starting with '#' character
7395      */
7396     if(cps->debug && *message == '#') return;
7397
7398     /*
7399      * Look for book output
7400      */
7401     if (cps == &first && bookRequested) {
7402         if (message[0] == '\t' || message[0] == ' ') {
7403             /* Part of the book output is here; append it */
7404             strcat(bookOutput, message);
7405             strcat(bookOutput, "  \n");
7406             return;
7407         } else if (bookOutput[0] != NULLCHAR) {
7408             /* All of book output has arrived; display it */
7409             char *p = bookOutput;
7410             while (*p != NULLCHAR) {
7411                 if (*p == '\t') *p = ' ';
7412                 p++;
7413             }
7414             DisplayInformation(bookOutput);
7415             bookRequested = FALSE;
7416             /* Fall through to parse the current output */
7417         }
7418     }
7419
7420     /*
7421      * Look for machine move.
7422      */
7423     if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7424         (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7425     {
7426         /* This method is only useful on engines that support ping */
7427         if (cps->lastPing != cps->lastPong) {
7428           if (gameMode == BeginningOfGame) {
7429             /* Extra move from before last new; ignore */
7430             if (appData.debugMode) {
7431                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7432             }
7433           } else {
7434             if (appData.debugMode) {
7435                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7436                         cps->which, gameMode);
7437             }
7438
7439             SendToProgram("undo\n", cps);
7440           }
7441           return;
7442         }
7443
7444         switch (gameMode) {
7445           case BeginningOfGame:
7446             /* Extra move from before last reset; ignore */
7447             if (appData.debugMode) {
7448                 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7449             }
7450             return;
7451
7452           case EndOfGame:
7453           case IcsIdle:
7454           default:
7455             /* Extra move after we tried to stop.  The mode test is
7456                not a reliable way of detecting this problem, but it's
7457                the best we can do on engines that don't support ping.
7458             */
7459             if (appData.debugMode) {
7460                 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7461                         cps->which, gameMode);
7462             }
7463             SendToProgram("undo\n", cps);
7464             return;
7465
7466           case MachinePlaysWhite:
7467           case IcsPlayingWhite:
7468             machineWhite = TRUE;
7469             break;
7470
7471           case MachinePlaysBlack:
7472           case IcsPlayingBlack:
7473             machineWhite = FALSE;
7474             break;
7475
7476           case TwoMachinesPlay:
7477             machineWhite = (cps->twoMachinesColor[0] == 'w');
7478             break;
7479         }
7480         if (WhiteOnMove(forwardMostMove) != machineWhite) {
7481             if (appData.debugMode) {
7482                 fprintf(debugFP,
7483                         "Ignoring move out of turn by %s, gameMode %d"
7484                         ", forwardMost %d\n",
7485                         cps->which, gameMode, forwardMostMove);
7486             }
7487             return;
7488         }
7489
7490     if (appData.debugMode) { int f = forwardMostMove;
7491         fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7492                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7493                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7494     }
7495         if(cps->alphaRank) AlphaRank(machineMove, 4);
7496         if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7497                               &fromX, &fromY, &toX, &toY, &promoChar)) {
7498             /* Machine move could not be parsed; ignore it. */
7499           snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7500                     machineMove, _(cps->which));
7501             DisplayError(buf1, 0);
7502             snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7503                     machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7504             if (gameMode == TwoMachinesPlay) {
7505               GameEnds(machineWhite ? BlackWins : WhiteWins,
7506                        buf1, GE_XBOARD);
7507             }
7508             return;
7509         }
7510
7511         /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7512         /* So we have to redo legality test with true e.p. status here,  */
7513         /* to make sure an illegal e.p. capture does not slip through,   */
7514         /* to cause a forfeit on a justified illegal-move complaint      */
7515         /* of the opponent.                                              */
7516         if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7517            ChessMove moveType;
7518            moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7519                              fromY, fromX, toY, toX, promoChar);
7520             if (appData.debugMode) {
7521                 int i;
7522                 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7523                     boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7524                 fprintf(debugFP, "castling rights\n");
7525             }
7526             if(moveType == IllegalMove) {
7527               snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7528                         machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7529                 GameEnds(machineWhite ? BlackWins : WhiteWins,
7530                            buf1, GE_XBOARD);
7531                 return;
7532            } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7533            /* [HGM] Kludge to handle engines that send FRC-style castling
7534               when they shouldn't (like TSCP-Gothic) */
7535            switch(moveType) {
7536              case WhiteASideCastleFR:
7537              case BlackASideCastleFR:
7538                toX+=2;
7539                currentMoveString[2]++;
7540                break;
7541              case WhiteHSideCastleFR:
7542              case BlackHSideCastleFR:
7543                toX--;
7544                currentMoveString[2]--;
7545                break;
7546              default: ; // nothing to do, but suppresses warning of pedantic compilers
7547            }
7548         }
7549         hintRequested = FALSE;
7550         lastHint[0] = NULLCHAR;
7551         bookRequested = FALSE;
7552         /* Program may be pondering now */
7553         cps->maybeThinking = TRUE;
7554         if (cps->sendTime == 2) cps->sendTime = 1;
7555         if (cps->offeredDraw) cps->offeredDraw--;
7556
7557         /* [AS] Save move info*/
7558         pvInfoList[ forwardMostMove ].score = programStats.score;
7559         pvInfoList[ forwardMostMove ].depth = programStats.depth;
7560         pvInfoList[ forwardMostMove ].time =  programStats.time; // [HGM] PGNtime: take time from engine stats
7561
7562         MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7563
7564         /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7565         if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7566             int count = 0;
7567
7568             while( count < adjudicateLossPlies ) {
7569                 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7570
7571                 if( count & 1 ) {
7572                     score = -score; /* Flip score for winning side */
7573                 }
7574
7575                 if( score > adjudicateLossThreshold ) {
7576                     break;
7577                 }
7578
7579                 count++;
7580             }
7581
7582             if( count >= adjudicateLossPlies ) {
7583                 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7584
7585                 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7586                     "Xboard adjudication",
7587                     GE_XBOARD );
7588
7589                 return;
7590             }
7591         }
7592
7593         if(Adjudicate(cps)) {
7594             ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7595             return; // [HGM] adjudicate: for all automatic game ends
7596         }
7597
7598 #if ZIPPY
7599         if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7600             first.initDone) {
7601           if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7602                 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7603                 SendToICS("draw ");
7604                 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7605           }
7606           SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7607           ics_user_moved = 1;
7608           if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7609                 char buf[3*MSG_SIZ];
7610
7611                 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7612                         programStats.score / 100.,
7613                         programStats.depth,
7614                         programStats.time / 100.,
7615                         (unsigned int)programStats.nodes,
7616                         (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7617                         programStats.movelist);
7618                 SendToICS(buf);
7619 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7620           }
7621         }
7622 #endif
7623
7624         /* [AS] Clear stats for next move */
7625         ClearProgramStats();
7626         thinkOutput[0] = NULLCHAR;
7627         hiddenThinkOutputState = 0;
7628
7629         bookHit = NULL;
7630         if (gameMode == TwoMachinesPlay) {
7631             /* [HGM] relaying draw offers moved to after reception of move */
7632             /* and interpreting offer as claim if it brings draw condition */
7633             if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7634                 SendToProgram("draw\n", cps->other);
7635             }
7636             if (cps->other->sendTime) {
7637                 SendTimeRemaining(cps->other,
7638                                   cps->other->twoMachinesColor[0] == 'w');
7639             }
7640             bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7641             if (firstMove && !bookHit) {
7642                 firstMove = FALSE;
7643                 if (cps->other->useColors) {
7644                   SendToProgram(cps->other->twoMachinesColor, cps->other);
7645                 }
7646                 SendToProgram("go\n", cps->other);
7647             }
7648             cps->other->maybeThinking = TRUE;
7649         }
7650
7651         ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7652
7653         if (!pausing && appData.ringBellAfterMoves) {
7654             RingBell();
7655         }
7656
7657         /*
7658          * Reenable menu items that were disabled while
7659          * machine was thinking
7660          */
7661         if (gameMode != TwoMachinesPlay)
7662             SetUserThinkingEnables();
7663
7664         // [HGM] book: after book hit opponent has received move and is now in force mode
7665         // force the book reply into it, and then fake that it outputted this move by jumping
7666         // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7667         if(bookHit) {
7668                 static char bookMove[MSG_SIZ]; // a bit generous?
7669
7670                 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7671                 strcat(bookMove, bookHit);
7672                 message = bookMove;
7673                 cps = cps->other;
7674                 programStats.nodes = programStats.depth = programStats.time =
7675                 programStats.score = programStats.got_only_move = 0;
7676                 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7677
7678                 if(cps->lastPing != cps->lastPong) {
7679                     savedMessage = message; // args for deferred call
7680                     savedState = cps;
7681                     ScheduleDelayedEvent(DeferredBookMove, 10);
7682                     return;
7683                 }
7684                 goto FakeBookMove;
7685         }
7686
7687         return;
7688     }
7689
7690     /* Set special modes for chess engines.  Later something general
7691      *  could be added here; for now there is just one kludge feature,
7692      *  needed because Crafty 15.10 and earlier don't ignore SIGINT
7693      *  when "xboard" is given as an interactive command.
7694      */
7695     if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7696         cps->useSigint = FALSE;
7697         cps->useSigterm = FALSE;
7698     }
7699     if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7700       ParseFeatures(message+8, cps);
7701       return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7702     }
7703
7704     if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7705       int dummy, s=6; char buf[MSG_SIZ];
7706       if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7707       if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7708       ParseFEN(boards[0], &dummy, message+s);
7709       DrawPosition(TRUE, boards[0]);
7710       startedFromSetupPosition = TRUE;
7711       return;
7712     }
7713     /* [HGM] Allow engine to set up a position. Don't ask me why one would
7714      * want this, I was asked to put it in, and obliged.
7715      */
7716     if (!strncmp(message, "setboard ", 9)) {
7717         Board initial_position;
7718
7719         GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7720
7721         if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7722             DisplayError(_("Bad FEN received from engine"), 0);
7723             return ;
7724         } else {
7725            Reset(TRUE, FALSE);
7726            CopyBoard(boards[0], initial_position);
7727            initialRulePlies = FENrulePlies;
7728            if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7729            else gameMode = MachinePlaysBlack;
7730            DrawPosition(FALSE, boards[currentMove]);
7731         }
7732         return;
7733     }
7734
7735     /*
7736      * Look for communication commands
7737      */
7738     if (!strncmp(message, "telluser ", 9)) {
7739         if(message[9] == '\\' && message[10] == '\\')
7740             EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7741         DisplayNote(message + 9);
7742         return;
7743     }
7744     if (!strncmp(message, "tellusererror ", 14)) {
7745         cps->userError = 1;
7746         if(message[14] == '\\' && message[15] == '\\')
7747             EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7748         DisplayError(message + 14, 0);
7749         return;
7750     }
7751     if (!strncmp(message, "tellopponent ", 13)) {
7752       if (appData.icsActive) {
7753         if (loggedOn) {
7754           snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7755           SendToICS(buf1);
7756         }
7757       } else {
7758         DisplayNote(message + 13);
7759       }
7760       return;
7761     }
7762     if (!strncmp(message, "tellothers ", 11)) {
7763       if (appData.icsActive) {
7764         if (loggedOn) {
7765           snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7766           SendToICS(buf1);
7767         }
7768       }
7769       return;
7770     }
7771     if (!strncmp(message, "tellall ", 8)) {
7772       if (appData.icsActive) {
7773         if (loggedOn) {
7774           snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7775           SendToICS(buf1);
7776         }
7777       } else {
7778         DisplayNote(message + 8);
7779       }
7780       return;
7781     }
7782     if (strncmp(message, "warning", 7) == 0) {
7783         /* Undocumented feature, use tellusererror in new code */
7784         DisplayError(message, 0);
7785         return;
7786     }
7787     if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7788         safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7789         strcat(realname, " query");
7790         AskQuestion(realname, buf2, buf1, cps->pr);
7791         return;
7792     }
7793     /* Commands from the engine directly to ICS.  We don't allow these to be
7794      *  sent until we are logged on. Crafty kibitzes have been known to
7795      *  interfere with the login process.
7796      */
7797     if (loggedOn) {
7798         if (!strncmp(message, "tellics ", 8)) {
7799             SendToICS(message + 8);
7800             SendToICS("\n");
7801             return;
7802         }
7803         if (!strncmp(message, "tellicsnoalias ", 15)) {
7804             SendToICS(ics_prefix);
7805             SendToICS(message + 15);
7806             SendToICS("\n");
7807             return;
7808         }
7809         /* The following are for backward compatibility only */
7810         if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7811             !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7812             SendToICS(ics_prefix);
7813             SendToICS(message);
7814             SendToICS("\n");
7815             return;
7816         }
7817     }
7818     if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
7819         return;
7820     }
7821     /*
7822      * If the move is illegal, cancel it and redraw the board.
7823      * Also deal with other error cases.  Matching is rather loose
7824      * here to accommodate engines written before the spec.
7825      */
7826     if (strncmp(message + 1, "llegal move", 11) == 0 ||
7827         strncmp(message, "Error", 5) == 0) {
7828         if (StrStr(message, "name") ||
7829             StrStr(message, "rating") || StrStr(message, "?") ||
7830             StrStr(message, "result") || StrStr(message, "board") ||
7831             StrStr(message, "bk") || StrStr(message, "computer") ||
7832             StrStr(message, "variant") || StrStr(message, "hint") ||
7833             StrStr(message, "random") || StrStr(message, "depth") ||
7834             StrStr(message, "accepted")) {
7835             return;
7836         }
7837         if (StrStr(message, "protover")) {
7838           /* Program is responding to input, so it's apparently done
7839              initializing, and this error message indicates it is
7840              protocol version 1.  So we don't need to wait any longer
7841              for it to initialize and send feature commands. */
7842           FeatureDone(cps, 1);
7843           cps->protocolVersion = 1;
7844           return;
7845         }
7846         cps->maybeThinking = FALSE;
7847
7848         if (StrStr(message, "draw")) {
7849             /* Program doesn't have "draw" command */
7850             cps->sendDrawOffers = 0;
7851             return;
7852         }
7853         if (cps->sendTime != 1 &&
7854             (StrStr(message, "time") || StrStr(message, "otim"))) {
7855           /* Program apparently doesn't have "time" or "otim" command */
7856           cps->sendTime = 0;
7857           return;
7858         }
7859         if (StrStr(message, "analyze")) {
7860             cps->analysisSupport = FALSE;
7861             cps->analyzing = FALSE;
7862             Reset(FALSE, TRUE);
7863             snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
7864             DisplayError(buf2, 0);
7865             return;
7866         }
7867         if (StrStr(message, "(no matching move)st")) {
7868           /* Special kludge for GNU Chess 4 only */
7869           cps->stKludge = TRUE;
7870           SendTimeControl(cps, movesPerSession, timeControl,
7871                           timeIncrement, appData.searchDepth,
7872                           searchTime);
7873           return;
7874         }
7875         if (StrStr(message, "(no matching move)sd")) {
7876           /* Special kludge for GNU Chess 4 only */
7877           cps->sdKludge = TRUE;
7878           SendTimeControl(cps, movesPerSession, timeControl,
7879                           timeIncrement, appData.searchDepth,
7880                           searchTime);
7881           return;
7882         }
7883         if (!StrStr(message, "llegal")) {
7884             return;
7885         }
7886         if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7887             gameMode == IcsIdle) return;
7888         if (forwardMostMove <= backwardMostMove) return;
7889         if (pausing) PauseEvent();
7890       if(appData.forceIllegal) {
7891             // [HGM] illegal: machine refused move; force position after move into it
7892           SendToProgram("force\n", cps);
7893           if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
7894                 // we have a real problem now, as SendBoard will use the a2a3 kludge
7895                 // when black is to move, while there might be nothing on a2 or black
7896                 // might already have the move. So send the board as if white has the move.
7897                 // But first we must change the stm of the engine, as it refused the last move
7898                 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
7899                 if(WhiteOnMove(forwardMostMove)) {
7900                     SendToProgram("a7a6\n", cps); // for the engine black still had the move
7901                     SendBoard(cps, forwardMostMove); // kludgeless board
7902                 } else {
7903                     SendToProgram("a2a3\n", cps); // for the engine white still had the move
7904                     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
7905                     SendBoard(cps, forwardMostMove+1); // kludgeless board
7906                 }
7907           } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
7908             if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
7909                  gameMode == TwoMachinesPlay)
7910               SendToProgram("go\n", cps);
7911             return;
7912       } else
7913         if (gameMode == PlayFromGameFile) {
7914             /* Stop reading this game file */
7915             gameMode = EditGame;
7916             ModeHighlight();
7917         }
7918         /* [HGM] illegal-move claim should forfeit game when Xboard */
7919         /* only passes fully legal moves                            */
7920         if( appData.testLegality && gameMode == TwoMachinesPlay ) {
7921             GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
7922                                 "False illegal-move claim", GE_XBOARD );
7923             return; // do not take back move we tested as valid
7924         }
7925         currentMove = forwardMostMove-1;
7926         DisplayMove(currentMove-1); /* before DisplayMoveError */
7927         SwitchClocks(forwardMostMove-1); // [HGM] race
7928         DisplayBothClocks();
7929         snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
7930                 parseList[currentMove], _(cps->which));
7931         DisplayMoveError(buf1);
7932         DrawPosition(FALSE, boards[currentMove]);
7933         return;
7934     }
7935     if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
7936         /* Program has a broken "time" command that
7937            outputs a string not ending in newline.
7938            Don't use it. */
7939         cps->sendTime = 0;
7940     }
7941
7942     /*
7943      * If chess program startup fails, exit with an error message.
7944      * Attempts to recover here are futile.
7945      */
7946     if ((StrStr(message, "unknown host") != NULL)
7947         || (StrStr(message, "No remote directory") != NULL)
7948         || (StrStr(message, "not found") != NULL)
7949         || (StrStr(message, "No such file") != NULL)
7950         || (StrStr(message, "can't alloc") != NULL)
7951         || (StrStr(message, "Permission denied") != NULL)) {
7952
7953         cps->maybeThinking = FALSE;
7954         snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
7955                 _(cps->which), cps->program, cps->host, message);
7956         RemoveInputSource(cps->isr);
7957         if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
7958             if(cps == &first) appData.noChessProgram = TRUE;
7959             DisplayError(buf1, 0);
7960         }
7961         return;
7962     }
7963
7964     /*
7965      * Look for hint output
7966      */
7967     if (sscanf(message, "Hint: %s", buf1) == 1) {
7968         if (cps == &first && hintRequested) {
7969             hintRequested = FALSE;
7970             if (ParseOneMove(buf1, forwardMostMove, &moveType,
7971                                  &fromX, &fromY, &toX, &toY, &promoChar)) {
7972                 (void) CoordsToAlgebraic(boards[forwardMostMove],
7973                                     PosFlags(forwardMostMove),
7974                                     fromY, fromX, toY, toX, promoChar, buf1);
7975                 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
7976                 DisplayInformation(buf2);
7977             } else {
7978                 /* Hint move could not be parsed!? */
7979               snprintf(buf2, sizeof(buf2),
7980                         _("Illegal hint move \"%s\"\nfrom %s chess program"),
7981                         buf1, _(cps->which));
7982                 DisplayError(buf2, 0);
7983             }
7984         } else {
7985           safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
7986         }
7987         return;
7988     }
7989
7990     /*
7991      * Ignore other messages if game is not in progress
7992      */
7993     if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
7994         gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
7995
7996     /*
7997      * look for win, lose, draw, or draw offer
7998      */
7999     if (strncmp(message, "1-0", 3) == 0) {
8000         char *p, *q, *r = "";
8001         p = strchr(message, '{');
8002         if (p) {
8003             q = strchr(p, '}');
8004             if (q) {
8005                 *q = NULLCHAR;
8006                 r = p + 1;
8007             }
8008         }
8009         GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8010         return;
8011     } else if (strncmp(message, "0-1", 3) == 0) {
8012         char *p, *q, *r = "";
8013         p = strchr(message, '{');
8014         if (p) {
8015             q = strchr(p, '}');
8016             if (q) {
8017                 *q = NULLCHAR;
8018                 r = p + 1;
8019             }
8020         }
8021         /* Kludge for Arasan 4.1 bug */
8022         if (strcmp(r, "Black resigns") == 0) {
8023             GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8024             return;
8025         }
8026         GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8027         return;
8028     } else if (strncmp(message, "1/2", 3) == 0) {
8029         char *p, *q, *r = "";
8030         p = strchr(message, '{');
8031         if (p) {
8032             q = strchr(p, '}');
8033             if (q) {
8034                 *q = NULLCHAR;
8035                 r = p + 1;
8036             }
8037         }
8038
8039         GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8040         return;
8041
8042     } else if (strncmp(message, "White resign", 12) == 0) {
8043         GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8044         return;
8045     } else if (strncmp(message, "Black resign", 12) == 0) {
8046         GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8047         return;
8048     } else if (strncmp(message, "White matches", 13) == 0 ||
8049                strncmp(message, "Black matches", 13) == 0   ) {
8050         /* [HGM] ignore GNUShogi noises */
8051         return;
8052     } else if (strncmp(message, "White", 5) == 0 &&
8053                message[5] != '(' &&
8054                StrStr(message, "Black") == NULL) {
8055         GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8056         return;
8057     } else if (strncmp(message, "Black", 5) == 0 &&
8058                message[5] != '(') {
8059         GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8060         return;
8061     } else if (strcmp(message, "resign") == 0 ||
8062                strcmp(message, "computer resigns") == 0) {
8063         switch (gameMode) {
8064           case MachinePlaysBlack:
8065           case IcsPlayingBlack:
8066             GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8067             break;
8068           case MachinePlaysWhite:
8069           case IcsPlayingWhite:
8070             GameEnds(BlackWins, "White resigns", GE_ENGINE);
8071             break;
8072           case TwoMachinesPlay:
8073             if (cps->twoMachinesColor[0] == 'w')
8074               GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8075             else
8076               GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8077             break;
8078           default:
8079             /* can't happen */
8080             break;
8081         }
8082         return;
8083     } else if (strncmp(message, "opponent mates", 14) == 0) {
8084         switch (gameMode) {
8085           case MachinePlaysBlack:
8086           case IcsPlayingBlack:
8087             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8088             break;
8089           case MachinePlaysWhite:
8090           case IcsPlayingWhite:
8091             GameEnds(BlackWins, "Black mates", GE_ENGINE);
8092             break;
8093           case TwoMachinesPlay:
8094             if (cps->twoMachinesColor[0] == 'w')
8095               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8096             else
8097               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8098             break;
8099           default:
8100             /* can't happen */
8101             break;
8102         }
8103         return;
8104     } else if (strncmp(message, "computer mates", 14) == 0) {
8105         switch (gameMode) {
8106           case MachinePlaysBlack:
8107           case IcsPlayingBlack:
8108             GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8109             break;
8110           case MachinePlaysWhite:
8111           case IcsPlayingWhite:
8112             GameEnds(WhiteWins, "White mates", GE_ENGINE);
8113             break;
8114           case TwoMachinesPlay:
8115             if (cps->twoMachinesColor[0] == 'w')
8116               GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8117             else
8118               GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8119             break;
8120           default:
8121             /* can't happen */
8122             break;
8123         }
8124         return;
8125     } else if (strncmp(message, "checkmate", 9) == 0) {
8126         if (WhiteOnMove(forwardMostMove)) {
8127             GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8128         } else {
8129             GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8130         }
8131         return;
8132     } else if (strstr(message, "Draw") != NULL ||
8133                strstr(message, "game is a draw") != NULL) {
8134         GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8135         return;
8136     } else if (strstr(message, "offer") != NULL &&
8137                strstr(message, "draw") != NULL) {
8138 #if ZIPPY
8139         if (appData.zippyPlay && first.initDone) {
8140             /* Relay offer to ICS */
8141             SendToICS(ics_prefix);
8142             SendToICS("draw\n");
8143         }
8144 #endif
8145         cps->offeredDraw = 2; /* valid until this engine moves twice */
8146         if (gameMode == TwoMachinesPlay) {
8147             if (cps->other->offeredDraw) {
8148                 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8149             /* [HGM] in two-machine mode we delay relaying draw offer      */
8150             /* until after we also have move, to see if it is really claim */
8151             }
8152         } else if (gameMode == MachinePlaysWhite ||
8153                    gameMode == MachinePlaysBlack) {
8154           if (userOfferedDraw) {
8155             DisplayInformation(_("Machine accepts your draw offer"));
8156             GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8157           } else {
8158             DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8159           }
8160         }
8161     }
8162
8163
8164     /*
8165      * Look for thinking output
8166      */
8167     if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8168           || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8169                                 ) {
8170         int plylev, mvleft, mvtot, curscore, time;
8171         char mvname[MOVE_LEN];
8172         u64 nodes; // [DM]
8173         char plyext;
8174         int ignore = FALSE;
8175         int prefixHint = FALSE;
8176         mvname[0] = NULLCHAR;
8177
8178         switch (gameMode) {
8179           case MachinePlaysBlack:
8180           case IcsPlayingBlack:
8181             if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8182             break;
8183           case MachinePlaysWhite:
8184           case IcsPlayingWhite:
8185             if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8186             break;
8187           case AnalyzeMode:
8188           case AnalyzeFile:
8189             break;
8190           case IcsObserving: /* [DM] icsEngineAnalyze */
8191             if (!appData.icsEngineAnalyze) ignore = TRUE;
8192             break;
8193           case TwoMachinesPlay:
8194             if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8195                 ignore = TRUE;
8196             }
8197             break;
8198           default:
8199             ignore = TRUE;
8200             break;
8201         }
8202
8203         if (!ignore) {
8204             ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8205             buf1[0] = NULLCHAR;
8206             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8207                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8208
8209                 if (plyext != ' ' && plyext != '\t') {
8210                     time *= 100;
8211                 }
8212
8213                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8214                 if( cps->scoreIsAbsolute &&
8215                     ( gameMode == MachinePlaysBlack ||
8216                       gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8217                       gameMode == IcsPlayingBlack ||     // [HGM] also add other situations where engine should report black POV
8218                      (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8219                      !WhiteOnMove(currentMove)
8220                     ) )
8221                 {
8222                     curscore = -curscore;
8223                 }
8224
8225
8226                 tempStats.depth = plylev;
8227                 tempStats.nodes = nodes;
8228                 tempStats.time = time;
8229                 tempStats.score = curscore;
8230                 tempStats.got_only_move = 0;
8231
8232                 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8233                         int ticklen;
8234
8235                         if(cps->nps == 0) ticklen = 10*time;                    // use engine reported time
8236                         else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8237                         if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8238                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8239                              whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8240                         if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8241                                                 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8242                              blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8243                 }
8244
8245                 /* Buffer overflow protection */
8246                 if (buf1[0] != NULLCHAR) {
8247                     if (strlen(buf1) >= sizeof(tempStats.movelist)
8248                         && appData.debugMode) {
8249                         fprintf(debugFP,
8250                                 "PV is too long; using the first %u bytes.\n",
8251                                 (unsigned) sizeof(tempStats.movelist) - 1);
8252                     }
8253
8254                     safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8255                 } else {
8256                     sprintf(tempStats.movelist, " no PV\n");
8257                 }
8258
8259                 if (tempStats.seen_stat) {
8260                     tempStats.ok_to_send = 1;
8261                 }
8262
8263                 if (strchr(tempStats.movelist, '(') != NULL) {
8264                     tempStats.line_is_book = 1;
8265                     tempStats.nr_moves = 0;
8266                     tempStats.moves_left = 0;
8267                 } else {
8268                     tempStats.line_is_book = 0;
8269                 }
8270
8271                     if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8272                         programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8273
8274                 SendProgramStatsToFrontend( cps, &tempStats );
8275
8276                 /*
8277                     [AS] Protect the thinkOutput buffer from overflow... this
8278                     is only useful if buf1 hasn't overflowed first!
8279                 */
8280                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8281                          plylev,
8282                          (gameMode == TwoMachinesPlay ?
8283                           ToUpper(cps->twoMachinesColor[0]) : ' '),
8284                          ((double) curscore) / 100.0,
8285                          prefixHint ? lastHint : "",
8286                          prefixHint ? " " : "" );
8287
8288                 if( buf1[0] != NULLCHAR ) {
8289                     unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8290
8291                     if( strlen(buf1) > max_len ) {
8292                         if( appData.debugMode) {
8293                             fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8294                         }
8295                         buf1[max_len+1] = '\0';
8296                     }
8297
8298                     strcat( thinkOutput, buf1 );
8299                 }
8300
8301                 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8302                         || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8303                     DisplayMove(currentMove - 1);
8304                 }
8305                 return;
8306
8307             } else if ((p=StrStr(message, "(only move)")) != NULL) {
8308                 /* crafty (9.25+) says "(only move) <move>"
8309                  * if there is only 1 legal move
8310                  */
8311                 sscanf(p, "(only move) %s", buf1);
8312                 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8313                 sprintf(programStats.movelist, "%s (only move)", buf1);
8314                 programStats.depth = 1;
8315                 programStats.nr_moves = 1;
8316                 programStats.moves_left = 1;
8317                 programStats.nodes = 1;
8318                 programStats.time = 1;
8319                 programStats.got_only_move = 1;
8320
8321                 /* Not really, but we also use this member to
8322                    mean "line isn't going to change" (Crafty
8323                    isn't searching, so stats won't change) */
8324                 programStats.line_is_book = 1;
8325
8326                 SendProgramStatsToFrontend( cps, &programStats );
8327
8328                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8329                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8330                     DisplayMove(currentMove - 1);
8331                 }
8332                 return;
8333             } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8334                               &time, &nodes, &plylev, &mvleft,
8335                               &mvtot, mvname) >= 5) {
8336                 /* The stat01: line is from Crafty (9.29+) in response
8337                    to the "." command */
8338                 programStats.seen_stat = 1;
8339                 cps->maybeThinking = TRUE;
8340
8341                 if (programStats.got_only_move || !appData.periodicUpdates)
8342                   return;
8343
8344                 programStats.depth = plylev;
8345                 programStats.time = time;
8346                 programStats.nodes = nodes;
8347                 programStats.moves_left = mvleft;
8348                 programStats.nr_moves = mvtot;
8349                 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8350                 programStats.ok_to_send = 1;
8351                 programStats.movelist[0] = '\0';
8352
8353                 SendProgramStatsToFrontend( cps, &programStats );
8354
8355                 return;
8356
8357             } else if (strncmp(message,"++",2) == 0) {
8358                 /* Crafty 9.29+ outputs this */
8359                 programStats.got_fail = 2;
8360                 return;
8361
8362             } else if (strncmp(message,"--",2) == 0) {
8363                 /* Crafty 9.29+ outputs this */
8364                 programStats.got_fail = 1;
8365                 return;
8366
8367             } else if (thinkOutput[0] != NULLCHAR &&
8368                        strncmp(message, "    ", 4) == 0) {
8369                 unsigned message_len;
8370
8371                 p = message;
8372                 while (*p && *p == ' ') p++;
8373
8374                 message_len = strlen( p );
8375
8376                 /* [AS] Avoid buffer overflow */
8377                 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8378                     strcat(thinkOutput, " ");
8379                     strcat(thinkOutput, p);
8380                 }
8381
8382                 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8383                     strcat(programStats.movelist, " ");
8384                     strcat(programStats.movelist, p);
8385                 }
8386
8387                 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8388                            gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8389                     DisplayMove(currentMove - 1);
8390                 }
8391                 return;
8392             }
8393         }
8394         else {
8395             buf1[0] = NULLCHAR;
8396
8397             if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8398                        &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8399             {
8400                 ChessProgramStats cpstats;
8401
8402                 if (plyext != ' ' && plyext != '\t') {
8403                     time *= 100;
8404                 }
8405
8406                 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8407                 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8408                     curscore = -curscore;
8409                 }
8410
8411                 cpstats.depth = plylev;
8412                 cpstats.nodes = nodes;
8413                 cpstats.time = time;
8414                 cpstats.score = curscore;
8415                 cpstats.got_only_move = 0;
8416                 cpstats.movelist[0] = '\0';
8417
8418                 if (buf1[0] != NULLCHAR) {
8419                     safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8420                 }
8421
8422                 cpstats.ok_to_send = 0;
8423                 cpstats.line_is_book = 0;
8424                 cpstats.nr_moves = 0;
8425                 cpstats.moves_left = 0;
8426
8427                 SendProgramStatsToFrontend( cps, &cpstats );
8428             }
8429         }
8430     }
8431 }
8432
8433
8434 /* Parse a game score from the character string "game", and
8435    record it as the history of the current game.  The game
8436    score is NOT assumed to start from the standard position.
8437    The display is not updated in any way.
8438    */
8439 void
8440 ParseGameHistory(game)
8441      char *game;
8442 {
8443     ChessMove moveType;
8444     int fromX, fromY, toX, toY, boardIndex;
8445     char promoChar;
8446     char *p, *q;
8447     char buf[MSG_SIZ];
8448
8449     if (appData.debugMode)
8450       fprintf(debugFP, "Parsing game history: %s\n", game);
8451
8452     if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8453     gameInfo.site = StrSave(appData.icsHost);
8454     gameInfo.date = PGNDate();
8455     gameInfo.round = StrSave("-");
8456
8457     /* Parse out names of players */
8458     while (*game == ' ') game++;
8459     p = buf;
8460     while (*game != ' ') *p++ = *game++;
8461     *p = NULLCHAR;
8462     gameInfo.white = StrSave(buf);
8463     while (*game == ' ') game++;
8464     p = buf;
8465     while (*game != ' ' && *game != '\n') *p++ = *game++;
8466     *p = NULLCHAR;
8467     gameInfo.black = StrSave(buf);
8468
8469     /* Parse moves */
8470     boardIndex = blackPlaysFirst ? 1 : 0;
8471     yynewstr(game);
8472     for (;;) {
8473         yyboardindex = boardIndex;
8474         moveType = (ChessMove) Myylex();
8475         switch (moveType) {
8476           case IllegalMove:             /* maybe suicide chess, etc. */
8477   if (appData.debugMode) {
8478     fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8479     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8480     setbuf(debugFP, NULL);
8481   }
8482           case WhitePromotion:
8483           case BlackPromotion:
8484           case WhiteNonPromotion:
8485           case BlackNonPromotion:
8486           case NormalMove:
8487           case WhiteCapturesEnPassant:
8488           case BlackCapturesEnPassant:
8489           case WhiteKingSideCastle:
8490           case WhiteQueenSideCastle:
8491           case BlackKingSideCastle:
8492           case BlackQueenSideCastle:
8493           case WhiteKingSideCastleWild:
8494           case WhiteQueenSideCastleWild:
8495           case BlackKingSideCastleWild:
8496           case BlackQueenSideCastleWild:
8497           /* PUSH Fabien */
8498           case WhiteHSideCastleFR:
8499           case WhiteASideCastleFR:
8500           case BlackHSideCastleFR:
8501           case BlackASideCastleFR:
8502           /* POP Fabien */
8503             fromX = currentMoveString[0] - AAA;
8504             fromY = currentMoveString[1] - ONE;
8505             toX = currentMoveString[2] - AAA;
8506             toY = currentMoveString[3] - ONE;
8507             promoChar = currentMoveString[4];
8508             break;
8509           case WhiteDrop:
8510           case BlackDrop:
8511             fromX = moveType == WhiteDrop ?
8512               (int) CharToPiece(ToUpper(currentMoveString[0])) :
8513             (int) CharToPiece(ToLower(currentMoveString[0]));
8514             fromY = DROP_RANK;
8515             toX = currentMoveString[2] - AAA;
8516             toY = currentMoveString[3] - ONE;
8517             promoChar = NULLCHAR;
8518             break;
8519           case AmbiguousMove:
8520             /* bug? */
8521             snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8522   if (appData.debugMode) {
8523     fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8524     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8525     setbuf(debugFP, NULL);
8526   }
8527             DisplayError(buf, 0);
8528             return;
8529           case ImpossibleMove:
8530             /* bug? */
8531             snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8532   if (appData.debugMode) {
8533     fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8534     fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8535     setbuf(debugFP, NULL);
8536   }
8537             DisplayError(buf, 0);
8538             return;
8539           case EndOfFile:
8540             if (boardIndex < backwardMostMove) {
8541                 /* Oops, gap.  How did that happen? */
8542                 DisplayError(_("Gap in move list"), 0);
8543                 return;
8544             }
8545             backwardMostMove =  blackPlaysFirst ? 1 : 0;
8546             if (boardIndex > forwardMostMove) {
8547                 forwardMostMove = boardIndex;
8548             }
8549             return;
8550           case ElapsedTime:
8551             if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8552                 strcat(parseList[boardIndex-1], " ");
8553                 strcat(parseList[boardIndex-1], yy_text);
8554             }
8555             continue;
8556           case Comment:
8557           case PGNTag:
8558           case NAG:
8559           default:
8560             /* ignore */
8561             continue;
8562           case WhiteWins:
8563           case BlackWins:
8564           case GameIsDrawn:
8565           case GameUnfinished:
8566             if (gameMode == IcsExamining) {
8567                 if (boardIndex < backwardMostMove) {
8568                     /* Oops, gap.  How did that happen? */
8569                     return;
8570                 }
8571                 backwardMostMove = blackPlaysFirst ? 1 : 0;
8572                 return;
8573             }
8574             gameInfo.result = moveType;
8575             p = strchr(yy_text, '{');
8576             if (p == NULL) p = strchr(yy_text, '(');
8577             if (p == NULL) {
8578                 p = yy_text;
8579                 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8580             } else {
8581                 q = strchr(p, *p == '{' ? '}' : ')');
8582                 if (q != NULL) *q = NULLCHAR;
8583                 p++;
8584             }
8585             while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8586             gameInfo.resultDetails = StrSave(p);
8587             continue;
8588         }
8589         if (boardIndex >= forwardMostMove &&
8590             !(gameMode == IcsObserving && ics_gamenum == -1)) {
8591             backwardMostMove = blackPlaysFirst ? 1 : 0;
8592             return;
8593         }
8594         (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8595                                  fromY, fromX, toY, toX, promoChar,
8596                                  parseList[boardIndex]);
8597         CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8598         /* currentMoveString is set as a side-effect of yylex */
8599         safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8600         strcat(moveList[boardIndex], "\n");
8601         boardIndex++;
8602         ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8603         switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8604           case MT_NONE:
8605           case MT_STALEMATE:
8606           default:
8607             break;
8608           case MT_CHECK:
8609             if(gameInfo.variant != VariantShogi)
8610                 strcat(parseList[boardIndex - 1], "+");
8611             break;
8612           case MT_CHECKMATE:
8613           case MT_STAINMATE:
8614             strcat(parseList[boardIndex - 1], "#");
8615             break;
8616         }
8617     }
8618 }
8619
8620
8621 /* Apply a move to the given board  */
8622 void
8623 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8624      int fromX, fromY, toX, toY;
8625      int promoChar;
8626      Board board;
8627 {
8628   ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8629   int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8630
8631     /* [HGM] compute & store e.p. status and castling rights for new position */
8632     /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8633
8634       if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8635       oldEP = (signed char)board[EP_STATUS];
8636       board[EP_STATUS] = EP_NONE;
8637
8638       if( board[toY][toX] != EmptySquare )
8639            board[EP_STATUS] = EP_CAPTURE;
8640
8641   if (fromY == DROP_RANK) {
8642         /* must be first */
8643         piece = board[toY][toX] = (ChessSquare) fromX;
8644   } else {
8645       int i;
8646
8647       if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8648            if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8649                board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8650       } else
8651       if( board[fromY][fromX] == WhitePawn ) {
8652            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8653                board[EP_STATUS] = EP_PAWN_MOVE;
8654            if( toY-fromY==2) {
8655                if(toX>BOARD_LEFT   && board[toY][toX-1] == BlackPawn &&
8656                         gameInfo.variant != VariantBerolina || toX < fromX)
8657                       board[EP_STATUS] = toX | berolina;
8658                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8659                         gameInfo.variant != VariantBerolina || toX > fromX)
8660                       board[EP_STATUS] = toX;
8661            }
8662       } else
8663       if( board[fromY][fromX] == BlackPawn ) {
8664            if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8665                board[EP_STATUS] = EP_PAWN_MOVE;
8666            if( toY-fromY== -2) {
8667                if(toX>BOARD_LEFT   && board[toY][toX-1] == WhitePawn &&
8668                         gameInfo.variant != VariantBerolina || toX < fromX)
8669                       board[EP_STATUS] = toX | berolina;
8670                if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8671                         gameInfo.variant != VariantBerolina || toX > fromX)
8672                       board[EP_STATUS] = toX;
8673            }
8674        }
8675
8676        for(i=0; i<nrCastlingRights; i++) {
8677            if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8678               board[CASTLING][i] == toX   && castlingRank[i] == toY
8679              ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8680        }
8681
8682      if (fromX == toX && fromY == toY) return;
8683
8684      piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8685      king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8686      if(gameInfo.variant == VariantKnightmate)
8687          king += (int) WhiteUnicorn - (int) WhiteKing;
8688
8689     /* Code added by Tord: */
8690     /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8691     if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8692         board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8693       board[fromY][fromX] = EmptySquare;
8694       board[toY][toX] = EmptySquare;
8695       if((toX > fromX) != (piece == WhiteRook)) {
8696         board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8697       } else {
8698         board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8699       }
8700     } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8701                board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8702       board[fromY][fromX] = EmptySquare;
8703       board[toY][toX] = EmptySquare;
8704       if((toX > fromX) != (piece == BlackRook)) {
8705         board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8706       } else {
8707         board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8708       }
8709     /* End of code added by Tord */
8710
8711     } else if (board[fromY][fromX] == king
8712         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8713         && toY == fromY && toX > fromX+1) {
8714         board[fromY][fromX] = EmptySquare;
8715         board[toY][toX] = king;
8716         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8717         board[fromY][BOARD_RGHT-1] = EmptySquare;
8718     } else if (board[fromY][fromX] == king
8719         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8720                && toY == fromY && toX < fromX-1) {
8721         board[fromY][fromX] = EmptySquare;
8722         board[toY][toX] = king;
8723         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8724         board[fromY][BOARD_LEFT] = EmptySquare;
8725     } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8726                 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8727                && toY >= BOARD_HEIGHT-promoRank
8728                ) {
8729         /* white pawn promotion */
8730         board[toY][toX] = CharToPiece(ToUpper(promoChar));
8731         if (board[toY][toX] == EmptySquare) {
8732             board[toY][toX] = WhiteQueen;
8733         }
8734         if(gameInfo.variant==VariantBughouse ||
8735            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8736             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8737         board[fromY][fromX] = EmptySquare;
8738     } else if ((fromY == BOARD_HEIGHT-4)
8739                && (toX != fromX)
8740                && gameInfo.variant != VariantXiangqi
8741                && gameInfo.variant != VariantBerolina
8742                && (board[fromY][fromX] == WhitePawn)
8743                && (board[toY][toX] == EmptySquare)) {
8744         board[fromY][fromX] = EmptySquare;
8745         board[toY][toX] = WhitePawn;
8746         captured = board[toY - 1][toX];
8747         board[toY - 1][toX] = EmptySquare;
8748     } else if ((fromY == BOARD_HEIGHT-4)
8749                && (toX == fromX)
8750                && gameInfo.variant == VariantBerolina
8751                && (board[fromY][fromX] == WhitePawn)
8752                && (board[toY][toX] == EmptySquare)) {
8753         board[fromY][fromX] = EmptySquare;
8754         board[toY][toX] = WhitePawn;
8755         if(oldEP & EP_BEROLIN_A) {
8756                 captured = board[fromY][fromX-1];
8757                 board[fromY][fromX-1] = EmptySquare;
8758         }else{  captured = board[fromY][fromX+1];
8759                 board[fromY][fromX+1] = EmptySquare;
8760         }
8761     } else if (board[fromY][fromX] == king
8762         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8763                && toY == fromY && toX > fromX+1) {
8764         board[fromY][fromX] = EmptySquare;
8765         board[toY][toX] = king;
8766         board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8767         board[fromY][BOARD_RGHT-1] = EmptySquare;
8768     } else if (board[fromY][fromX] == king
8769         && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8770                && toY == fromY && toX < fromX-1) {
8771         board[fromY][fromX] = EmptySquare;
8772         board[toY][toX] = king;
8773         board[toY][toX+1] = board[fromY][BOARD_LEFT];
8774         board[fromY][BOARD_LEFT] = EmptySquare;
8775     } else if (fromY == 7 && fromX == 3
8776                && board[fromY][fromX] == BlackKing
8777                && toY == 7 && toX == 5) {
8778         board[fromY][fromX] = EmptySquare;
8779         board[toY][toX] = BlackKing;
8780         board[fromY][7] = EmptySquare;
8781         board[toY][4] = BlackRook;
8782     } else if (fromY == 7 && fromX == 3
8783                && board[fromY][fromX] == BlackKing
8784                && toY == 7 && toX == 1) {
8785         board[fromY][fromX] = EmptySquare;
8786         board[toY][toX] = BlackKing;
8787         board[fromY][0] = EmptySquare;
8788         board[toY][2] = BlackRook;
8789     } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8790                 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8791                && toY < promoRank
8792                ) {
8793         /* black pawn promotion */
8794         board[toY][toX] = CharToPiece(ToLower(promoChar));
8795         if (board[toY][toX] == EmptySquare) {
8796             board[toY][toX] = BlackQueen;
8797         }
8798         if(gameInfo.variant==VariantBughouse ||
8799            gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8800             board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8801         board[fromY][fromX] = EmptySquare;
8802     } else if ((fromY == 3)
8803                && (toX != fromX)
8804                && gameInfo.variant != VariantXiangqi
8805                && gameInfo.variant != VariantBerolina
8806                && (board[fromY][fromX] == BlackPawn)
8807                && (board[toY][toX] == EmptySquare)) {
8808         board[fromY][fromX] = EmptySquare;
8809         board[toY][toX] = BlackPawn;
8810         captured = board[toY + 1][toX];
8811         board[toY + 1][toX] = EmptySquare;
8812     } else if ((fromY == 3)
8813                && (toX == fromX)
8814                && gameInfo.variant == VariantBerolina
8815                && (board[fromY][fromX] == BlackPawn)
8816                && (board[toY][toX] == EmptySquare)) {
8817         board[fromY][fromX] = EmptySquare;
8818         board[toY][toX] = BlackPawn;
8819         if(oldEP & EP_BEROLIN_A) {
8820                 captured = board[fromY][fromX-1];
8821                 board[fromY][fromX-1] = EmptySquare;
8822         }else{  captured = board[fromY][fromX+1];
8823                 board[fromY][fromX+1] = EmptySquare;
8824         }
8825     } else {
8826         board[toY][toX] = board[fromY][fromX];
8827         board[fromY][fromX] = EmptySquare;
8828     }
8829   }
8830
8831     if (gameInfo.holdingsWidth != 0) {
8832
8833       /* !!A lot more code needs to be written to support holdings  */
8834       /* [HGM] OK, so I have written it. Holdings are stored in the */
8835       /* penultimate board files, so they are automaticlly stored   */
8836       /* in the game history.                                       */
8837       if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
8838                                 && promoChar && piece != WhitePawn && piece != BlackPawn) {
8839         /* Delete from holdings, by decreasing count */
8840         /* and erasing image if necessary            */
8841         p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
8842         if(p < (int) BlackPawn) { /* white drop */
8843              p -= (int)WhitePawn;
8844                  p = PieceToNumber((ChessSquare)p);
8845              if(p >= gameInfo.holdingsSize) p = 0;
8846              if(--board[p][BOARD_WIDTH-2] <= 0)
8847                   board[p][BOARD_WIDTH-1] = EmptySquare;
8848              if((int)board[p][BOARD_WIDTH-2] < 0)
8849                         board[p][BOARD_WIDTH-2] = 0;
8850         } else {                  /* black drop */
8851              p -= (int)BlackPawn;
8852                  p = PieceToNumber((ChessSquare)p);
8853              if(p >= gameInfo.holdingsSize) p = 0;
8854              if(--board[BOARD_HEIGHT-1-p][1] <= 0)
8855                   board[BOARD_HEIGHT-1-p][0] = EmptySquare;
8856              if((int)board[BOARD_HEIGHT-1-p][1] < 0)
8857                         board[BOARD_HEIGHT-1-p][1] = 0;
8858         }
8859       }
8860       if (captured != EmptySquare && gameInfo.holdingsSize > 0
8861           && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess        ) {
8862         /* [HGM] holdings: Add to holdings, if holdings exist */
8863         if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
8864                 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
8865                 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
8866         }
8867         p = (int) captured;
8868         if (p >= (int) BlackPawn) {
8869           p -= (int)BlackPawn;
8870           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8871                   /* in Shogi restore piece to its original  first */
8872                   captured = (ChessSquare) (DEMOTED captured);
8873                   p = DEMOTED p;
8874           }
8875           p = PieceToNumber((ChessSquare)p);
8876           if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
8877           board[p][BOARD_WIDTH-2]++;
8878           board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
8879         } else {
8880           p -= (int)WhitePawn;
8881           if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
8882                   captured = (ChessSquare) (DEMOTED captured);
8883                   p = DEMOTED p;
8884           }
8885           p = PieceToNumber((ChessSquare)p);
8886           if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
8887           board[BOARD_HEIGHT-1-p][1]++;
8888           board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
8889         }
8890       }
8891     } else if (gameInfo.variant == VariantAtomic) {
8892       if (captured != EmptySquare) {
8893         int y, x;
8894         for (y = toY-1; y <= toY+1; y++) {
8895           for (x = toX-1; x <= toX+1; x++) {
8896             if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
8897                 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
8898               board[y][x] = EmptySquare;
8899             }
8900           }
8901         }
8902         board[toY][toX] = EmptySquare;
8903       }
8904     }
8905     if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
8906         board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
8907     } else
8908     if(promoChar == '+') {
8909         /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
8910         board[toY][toX] = (ChessSquare) (PROMOTED piece);
8911     } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
8912         board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
8913     }
8914     if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) 
8915                 && promoChar != NULLCHAR && gameInfo.holdingsSize) { 
8916         // [HGM] superchess: take promotion piece out of holdings
8917         int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
8918         if((int)piece < (int)BlackPawn) { // determine stm from piece color
8919             if(!--board[k][BOARD_WIDTH-2])
8920                 board[k][BOARD_WIDTH-1] = EmptySquare;
8921         } else {
8922             if(!--board[BOARD_HEIGHT-1-k][1])
8923                 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
8924         }
8925     }
8926
8927 }
8928
8929 /* Updates forwardMostMove */
8930 void
8931 MakeMove(fromX, fromY, toX, toY, promoChar)
8932      int fromX, fromY, toX, toY;
8933      int promoChar;
8934 {
8935 //    forwardMostMove++; // [HGM] bare: moved downstream
8936
8937     if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
8938         int timeLeft; static int lastLoadFlag=0; int king, piece;
8939         piece = boards[forwardMostMove][fromY][fromX];
8940         king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
8941         if(gameInfo.variant == VariantKnightmate)
8942             king += (int) WhiteUnicorn - (int) WhiteKing;
8943         if(forwardMostMove == 0) {
8944             if(blackPlaysFirst)
8945                 fprintf(serverMoves, "%s;", second.tidy);
8946             fprintf(serverMoves, "%s;", first.tidy);
8947             if(!blackPlaysFirst)
8948                 fprintf(serverMoves, "%s;", second.tidy);
8949         } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
8950         lastLoadFlag = loadFlag;
8951         // print base move
8952         fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
8953         // print castling suffix
8954         if( toY == fromY && piece == king ) {
8955             if(toX-fromX > 1)
8956                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
8957             if(fromX-toX >1)
8958                 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
8959         }
8960         // e.p. suffix
8961         if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
8962              boards[forwardMostMove][fromY][fromX] == BlackPawn   ) &&
8963              boards[forwardMostMove][toY][toX] == EmptySquare
8964              && fromX != toX && fromY != toY)
8965                 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
8966         // promotion suffix
8967         if(promoChar != NULLCHAR)
8968                 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
8969         if(!loadFlag) {
8970             fprintf(serverMoves, "/%d/%d",
8971                pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
8972             if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
8973             else                      timeLeft = blackTimeRemaining/1000;
8974             fprintf(serverMoves, "/%d", timeLeft);
8975         }
8976         fflush(serverMoves);
8977     }
8978
8979     if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
8980       DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
8981                         0, 1);
8982       return;
8983     }
8984     UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
8985     if (commentList[forwardMostMove+1] != NULL) {
8986         free(commentList[forwardMostMove+1]);
8987         commentList[forwardMostMove+1] = NULL;
8988     }
8989     CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8990     ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
8991     // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
8992     SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
8993     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
8994     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
8995     gameInfo.result = GameUnfinished;
8996     if (gameInfo.resultDetails != NULL) {
8997         free(gameInfo.resultDetails);
8998         gameInfo.resultDetails = NULL;
8999     }
9000     CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9001                               moveList[forwardMostMove - 1]);
9002     (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9003                              PosFlags(forwardMostMove - 1),
9004                              fromY, fromX, toY, toX, promoChar,
9005                              parseList[forwardMostMove - 1]);
9006     switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9007       case MT_NONE:
9008       case MT_STALEMATE:
9009       default:
9010         break;
9011       case MT_CHECK:
9012         if(gameInfo.variant != VariantShogi)
9013             strcat(parseList[forwardMostMove - 1], "+");
9014         break;
9015       case MT_CHECKMATE:
9016       case MT_STAINMATE:
9017         strcat(parseList[forwardMostMove - 1], "#");
9018         break;
9019     }
9020     if (appData.debugMode) {
9021         fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9022     }
9023
9024 }
9025
9026 /* Updates currentMove if not pausing */
9027 void
9028 ShowMove(fromX, fromY, toX, toY)
9029 {
9030     int instant = (gameMode == PlayFromGameFile) ?
9031         (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9032     if(appData.noGUI) return;
9033     if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9034         if (!instant) {
9035             if (forwardMostMove == currentMove + 1) {
9036                 AnimateMove(boards[forwardMostMove - 1],
9037                             fromX, fromY, toX, toY);
9038             }
9039             if (appData.highlightLastMove) {
9040                 SetHighlights(fromX, fromY, toX, toY);
9041             }
9042         }
9043         currentMove = forwardMostMove;
9044     }
9045
9046     if (instant) return;
9047
9048     DisplayMove(currentMove - 1);
9049     DrawPosition(FALSE, boards[currentMove]);
9050     DisplayBothClocks();
9051     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9052 }
9053
9054 void SendEgtPath(ChessProgramState *cps)
9055 {       /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9056         char buf[MSG_SIZ], name[MSG_SIZ], *p;
9057
9058         if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9059
9060         while(*p) {
9061             char c, *q = name+1, *r, *s;
9062
9063             name[0] = ','; // extract next format name from feature and copy with prefixed ','
9064             while(*p && *p != ',') *q++ = *p++;
9065             *q++ = ':'; *q = 0;
9066             if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9067                 strcmp(name, ",nalimov:") == 0 ) {
9068                 // take nalimov path from the menu-changeable option first, if it is defined
9069               snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9070                 SendToProgram(buf,cps);     // send egtbpath command for nalimov
9071             } else
9072             if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9073                 (s = StrStr(appData.egtFormats, name)) != NULL) {
9074                 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9075                 s = r = StrStr(s, ":") + 1; // beginning of path info
9076                 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9077                 c = *r; *r = 0;             // temporarily null-terminate path info
9078                     *--q = 0;               // strip of trailig ':' from name
9079                     snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9080                 *r = c;
9081                 SendToProgram(buf,cps);     // send egtbpath command for this format
9082             }
9083             if(*p == ',') p++; // read away comma to position for next format name
9084         }
9085 }
9086
9087 void
9088 InitChessProgram(cps, setup)
9089      ChessProgramState *cps;
9090      int setup; /* [HGM] needed to setup FRC opening position */
9091 {
9092     char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9093     if (appData.noChessProgram) return;
9094     hintRequested = FALSE;
9095     bookRequested = FALSE;
9096
9097     /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9098     /*       moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9099     if(cps->memSize) { /* [HGM] memory */
9100       snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9101         SendToProgram(buf, cps);
9102     }
9103     SendEgtPath(cps); /* [HGM] EGT */
9104     if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9105       snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9106         SendToProgram(buf, cps);
9107     }
9108
9109     SendToProgram(cps->initString, cps);
9110     if (gameInfo.variant != VariantNormal &&
9111         gameInfo.variant != VariantLoadable
9112         /* [HGM] also send variant if board size non-standard */
9113         || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9114                                             ) {
9115       char *v = VariantName(gameInfo.variant);
9116       if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9117         /* [HGM] in protocol 1 we have to assume all variants valid */
9118         snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9119         DisplayFatalError(buf, 0, 1);
9120         return;
9121       }
9122
9123       /* [HGM] make prefix for non-standard board size. Awkward testing... */
9124       overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9125       if( gameInfo.variant == VariantXiangqi )
9126            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9127       if( gameInfo.variant == VariantShogi )
9128            overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9129       if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9130            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9131       if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9132           gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9133            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9134       if( gameInfo.variant == VariantCourier )
9135            overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9136       if( gameInfo.variant == VariantSuper )
9137            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9138       if( gameInfo.variant == VariantGreat )
9139            overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9140       if( gameInfo.variant == VariantSChess )
9141            overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9142
9143       if(overruled) {
9144         snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9145                  gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9146            /* [HGM] varsize: try first if this defiant size variant is specifically known */
9147            if(StrStr(cps->variants, b) == NULL) {
9148                // specific sized variant not known, check if general sizing allowed
9149                if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9150                    if(StrStr(cps->variants, "boardsize") == NULL) {
9151                      snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9152                             gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9153                        DisplayFatalError(buf, 0, 1);
9154                        return;
9155                    }
9156                    /* [HGM] here we really should compare with the maximum supported board size */
9157                }
9158            }
9159       } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9160       snprintf(buf, MSG_SIZ, "variant %s\n", b);
9161       SendToProgram(buf, cps);
9162     }
9163     currentlyInitializedVariant = gameInfo.variant;
9164
9165     /* [HGM] send opening position in FRC to first engine */
9166     if(setup) {
9167           SendToProgram("force\n", cps);
9168           SendBoard(cps, 0);
9169           /* engine is now in force mode! Set flag to wake it up after first move. */
9170           setboardSpoiledMachineBlack = 1;
9171     }
9172
9173     if (cps->sendICS) {
9174       snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9175       SendToProgram(buf, cps);
9176     }
9177     cps->maybeThinking = FALSE;
9178     cps->offeredDraw = 0;
9179     if (!appData.icsActive) {
9180         SendTimeControl(cps, movesPerSession, timeControl,
9181                         timeIncrement, appData.searchDepth,
9182                         searchTime);
9183     }
9184     if (appData.showThinking
9185         // [HGM] thinking: four options require thinking output to be sent
9186         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9187                                 ) {
9188         SendToProgram("post\n", cps);
9189     }
9190     SendToProgram("hard\n", cps);
9191     if (!appData.ponderNextMove) {
9192         /* Warning: "easy" is a toggle in GNU Chess, so don't send
9193            it without being sure what state we are in first.  "hard"
9194            is not a toggle, so that one is OK.
9195          */
9196         SendToProgram("easy\n", cps);
9197     }
9198     if (cps->usePing) {
9199       snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9200       SendToProgram(buf, cps);
9201     }
9202     cps->initDone = TRUE;
9203 }
9204
9205
9206 void
9207 StartChessProgram(cps)
9208      ChessProgramState *cps;
9209 {
9210     char buf[MSG_SIZ];
9211     int err;
9212
9213     if (appData.noChessProgram) return;
9214     cps->initDone = FALSE;
9215
9216     if (strcmp(cps->host, "localhost") == 0) {
9217         err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9218     } else if (*appData.remoteShell == NULLCHAR) {
9219         err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9220     } else {
9221         if (*appData.remoteUser == NULLCHAR) {
9222           snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9223                     cps->program);
9224         } else {
9225           snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9226                     cps->host, appData.remoteUser, cps->program);
9227         }
9228         err = StartChildProcess(buf, "", &cps->pr);
9229     }
9230
9231     if (err != 0) {
9232       snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9233         DisplayFatalError(buf, err, 1);
9234         cps->pr = NoProc;
9235         cps->isr = NULL;
9236         return;
9237     }
9238
9239     cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9240     if (cps->protocolVersion > 1) {
9241       snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9242       cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9243       cps->comboCnt = 0;  //                and values of combo boxes
9244       SendToProgram(buf, cps);
9245     } else {
9246       SendToProgram("xboard\n", cps);
9247     }
9248 }
9249
9250 void
9251 TwoMachinesEventIfReady P((void))
9252 {
9253   if (first.lastPing != first.lastPong) {
9254     DisplayMessage("", _("Waiting for first chess program"));
9255     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9256     return;
9257   }
9258   if (second.lastPing != second.lastPong) {
9259     DisplayMessage("", _("Waiting for second chess program"));
9260     ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9261     return;
9262   }
9263   ThawUI();
9264   TwoMachinesEvent();
9265 }
9266
9267 void
9268 NextMatchGame P((void))
9269 {
9270     Reset(FALSE, TRUE);
9271     LoadGameOrPosition(matchGame);
9272     TwoMachinesEventIfReady();
9273 }
9274
9275 void UserAdjudicationEvent( int result )
9276 {
9277     ChessMove gameResult = GameIsDrawn;
9278
9279     if( result > 0 ) {
9280         gameResult = WhiteWins;
9281     }
9282     else if( result < 0 ) {
9283         gameResult = BlackWins;
9284     }
9285
9286     if( gameMode == TwoMachinesPlay ) {
9287         GameEnds( gameResult, "User adjudication", GE_XBOARD );
9288     }
9289 }
9290
9291
9292 // [HGM] save: calculate checksum of game to make games easily identifiable
9293 int StringCheckSum(char *s)
9294 {
9295         int i = 0;
9296         if(s==NULL) return 0;
9297         while(*s) i = i*259 + *s++;
9298         return i;
9299 }
9300
9301 int GameCheckSum()
9302 {
9303         int i, sum=0;
9304         for(i=backwardMostMove; i<forwardMostMove; i++) {
9305                 sum += pvInfoList[i].depth;
9306                 sum += StringCheckSum(parseList[i]);
9307                 sum += StringCheckSum(commentList[i]);
9308                 sum *= 261;
9309         }
9310         if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9311         return sum + StringCheckSum(commentList[i]);
9312 } // end of save patch
9313
9314 void
9315 GameEnds(result, resultDetails, whosays)
9316      ChessMove result;
9317      char *resultDetails;
9318      int whosays;
9319 {
9320     GameMode nextGameMode;
9321     int isIcsGame;
9322     char buf[MSG_SIZ], popupRequested = 0;
9323
9324     if(endingGame) return; /* [HGM] crash: forbid recursion */
9325     endingGame = 1;
9326     if(twoBoards) { // [HGM] dual: switch back to one board
9327         twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9328         DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9329     }
9330     if (appData.debugMode) {
9331       fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9332               result, resultDetails ? resultDetails : "(null)", whosays);
9333     }
9334
9335     fromX = fromY = -1; // [HGM] abort any move the user is entering.
9336
9337     if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9338         /* If we are playing on ICS, the server decides when the
9339            game is over, but the engine can offer to draw, claim
9340            a draw, or resign.
9341          */
9342 #if ZIPPY
9343         if (appData.zippyPlay && first.initDone) {
9344             if (result == GameIsDrawn) {
9345                 /* In case draw still needs to be claimed */
9346                 SendToICS(ics_prefix);
9347                 SendToICS("draw\n");
9348             } else if (StrCaseStr(resultDetails, "resign")) {
9349                 SendToICS(ics_prefix);
9350                 SendToICS("resign\n");
9351             }
9352         }
9353 #endif
9354         endingGame = 0; /* [HGM] crash */
9355         return;
9356     }
9357
9358     /* If we're loading the game from a file, stop */
9359     if (whosays == GE_FILE) {
9360       (void) StopLoadGameTimer();
9361       gameFileFP = NULL;
9362     }
9363
9364     /* Cancel draw offers */
9365     first.offeredDraw = second.offeredDraw = 0;
9366
9367     /* If this is an ICS game, only ICS can really say it's done;
9368        if not, anyone can. */
9369     isIcsGame = (gameMode == IcsPlayingWhite ||
9370                  gameMode == IcsPlayingBlack ||
9371                  gameMode == IcsObserving    ||
9372                  gameMode == IcsExamining);
9373
9374     if (!isIcsGame || whosays == GE_ICS) {
9375         /* OK -- not an ICS game, or ICS said it was done */
9376         StopClocks();
9377         if (!isIcsGame && !appData.noChessProgram)
9378           SetUserThinkingEnables();
9379
9380         /* [HGM] if a machine claims the game end we verify this claim */
9381         if(gameMode == TwoMachinesPlay && appData.testClaims) {
9382             if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9383                 char claimer;
9384                 ChessMove trueResult = (ChessMove) -1;
9385
9386                 claimer = whosays == GE_ENGINE1 ?      /* color of claimer */
9387                                             first.twoMachinesColor[0] :
9388                                             second.twoMachinesColor[0] ;
9389
9390                 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9391                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9392                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9393                     trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9394                 } else
9395                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9396                     /* [HGM] verify: engine mate claims accepted if they were flagged */
9397                     trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9398                 } else
9399                 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9400                     trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9401                 }
9402
9403                 // now verify win claims, but not in drop games, as we don't understand those yet
9404                 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9405                                                  || gameInfo.variant == VariantGreat) &&
9406                     (result == WhiteWins && claimer == 'w' ||
9407                      result == BlackWins && claimer == 'b'   ) ) { // case to verify: engine claims own win
9408                       if (appData.debugMode) {
9409                         fprintf(debugFP, "result=%d sp=%d move=%d\n",
9410                                 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9411                       }
9412                       if(result != trueResult) {
9413                         snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9414                               result = claimer == 'w' ? BlackWins : WhiteWins;
9415                               resultDetails = buf;
9416                       }
9417                 } else
9418                 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9419                     && (forwardMostMove <= backwardMostMove ||
9420                         (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9421                         (claimer=='b')==(forwardMostMove&1))
9422                                                                                   ) {
9423                       /* [HGM] verify: draws that were not flagged are false claims */
9424                   snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9425                       result = claimer == 'w' ? BlackWins : WhiteWins;
9426                       resultDetails = buf;
9427                 }
9428                 /* (Claiming a loss is accepted no questions asked!) */
9429             }
9430             /* [HGM] bare: don't allow bare King to win */
9431             if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9432                && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9433                && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9434                && result != GameIsDrawn)
9435             {   int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9436                 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9437                         int p = (signed char)boards[forwardMostMove][i][j] - color;
9438                         if(p >= 0 && p <= (int)WhiteKing) k++;
9439                 }
9440                 if (appData.debugMode) {
9441                      fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9442                         result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9443                 }
9444                 if(k <= 1) {
9445                         result = GameIsDrawn;
9446                         snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9447                         resultDetails = buf;
9448                 }
9449             }
9450         }
9451
9452
9453         if(serverMoves != NULL && !loadFlag) { char c = '=';
9454             if(result==WhiteWins) c = '+';
9455             if(result==BlackWins) c = '-';
9456             if(resultDetails != NULL)
9457                 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9458         }
9459         if (resultDetails != NULL) {
9460             gameInfo.result = result;
9461             gameInfo.resultDetails = StrSave(resultDetails);
9462
9463             /* display last move only if game was not loaded from file */
9464             if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9465                 DisplayMove(currentMove - 1);
9466
9467             if (forwardMostMove != 0) {
9468                 if (gameMode != PlayFromGameFile && gameMode != EditGame
9469                     && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9470                                                                 ) {
9471                     if (*appData.saveGameFile != NULLCHAR) {
9472                         SaveGameToFile(appData.saveGameFile, TRUE);
9473                     } else if (appData.autoSaveGames) {
9474                         AutoSaveGame();
9475                     }
9476                     if (*appData.savePositionFile != NULLCHAR) {
9477                         SavePositionToFile(appData.savePositionFile);
9478                     }
9479                 }
9480             }
9481
9482             /* Tell program how game ended in case it is learning */
9483             /* [HGM] Moved this to after saving the PGN, just in case */
9484             /* engine died and we got here through time loss. In that */
9485             /* case we will get a fatal error writing the pipe, which */
9486             /* would otherwise lose us the PGN.                       */
9487             /* [HGM] crash: not needed anymore, but doesn't hurt;     */
9488             /* output during GameEnds should never be fatal anymore   */
9489             if (gameMode == MachinePlaysWhite ||
9490                 gameMode == MachinePlaysBlack ||
9491                 gameMode == TwoMachinesPlay ||
9492                 gameMode == IcsPlayingWhite ||
9493                 gameMode == IcsPlayingBlack ||
9494                 gameMode == BeginningOfGame) {
9495                 char buf[MSG_SIZ];
9496                 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9497                         resultDetails);
9498                 if (first.pr != NoProc) {
9499                     SendToProgram(buf, &first);
9500                 }
9501                 if (second.pr != NoProc &&
9502                     gameMode == TwoMachinesPlay) {
9503                     SendToProgram(buf, &second);
9504                 }
9505             }
9506         }
9507
9508         if (appData.icsActive) {
9509             if (appData.quietPlay &&
9510                 (gameMode == IcsPlayingWhite ||
9511                  gameMode == IcsPlayingBlack)) {
9512                 SendToICS(ics_prefix);
9513                 SendToICS("set shout 1\n");
9514             }
9515             nextGameMode = IcsIdle;
9516             ics_user_moved = FALSE;
9517             /* clean up premove.  It's ugly when the game has ended and the
9518              * premove highlights are still on the board.
9519              */
9520             if (gotPremove) {
9521               gotPremove = FALSE;
9522               ClearPremoveHighlights();
9523               DrawPosition(FALSE, boards[currentMove]);
9524             }
9525             if (whosays == GE_ICS) {
9526                 switch (result) {
9527                 case WhiteWins:
9528                     if (gameMode == IcsPlayingWhite)
9529                         PlayIcsWinSound();
9530                     else if(gameMode == IcsPlayingBlack)
9531                         PlayIcsLossSound();
9532                     break;
9533                 case BlackWins:
9534                     if (gameMode == IcsPlayingBlack)
9535                         PlayIcsWinSound();
9536                     else if(gameMode == IcsPlayingWhite)
9537                         PlayIcsLossSound();
9538                     break;
9539                 case GameIsDrawn:
9540                     PlayIcsDrawSound();
9541                     break;
9542                 default:
9543                     PlayIcsUnfinishedSound();
9544                 }
9545             }
9546         } else if (gameMode == EditGame ||
9547                    gameMode == PlayFromGameFile ||
9548                    gameMode == AnalyzeMode ||
9549                    gameMode == AnalyzeFile) {
9550             nextGameMode = gameMode;
9551         } else {
9552             nextGameMode = EndOfGame;
9553         }
9554         pausing = FALSE;
9555         ModeHighlight();
9556     } else {
9557         nextGameMode = gameMode;
9558     }
9559
9560     if (appData.noChessProgram) {
9561         gameMode = nextGameMode;
9562         ModeHighlight();
9563         endingGame = 0; /* [HGM] crash */
9564         return;
9565     }
9566
9567     if (first.reuse) {
9568         /* Put first chess program into idle state */
9569         if (first.pr != NoProc &&
9570             (gameMode == MachinePlaysWhite ||
9571              gameMode == MachinePlaysBlack ||
9572              gameMode == TwoMachinesPlay ||
9573              gameMode == IcsPlayingWhite ||
9574              gameMode == IcsPlayingBlack ||
9575              gameMode == BeginningOfGame)) {
9576             SendToProgram("force\n", &first);
9577             if (first.usePing) {
9578               char buf[MSG_SIZ];
9579               snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9580               SendToProgram(buf, &first);
9581             }
9582         }
9583     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9584         /* Kill off first chess program */
9585         if (first.isr != NULL)
9586           RemoveInputSource(first.isr);
9587         first.isr = NULL;
9588
9589         if (first.pr != NoProc) {
9590             ExitAnalyzeMode();
9591             DoSleep( appData.delayBeforeQuit );
9592             SendToProgram("quit\n", &first);
9593             DoSleep( appData.delayAfterQuit );
9594             DestroyChildProcess(first.pr, first.useSigterm);
9595         }
9596         first.pr = NoProc;
9597     }
9598     if (second.reuse) {
9599         /* Put second chess program into idle state */
9600         if (second.pr != NoProc &&
9601             gameMode == TwoMachinesPlay) {
9602             SendToProgram("force\n", &second);
9603             if (second.usePing) {
9604               char buf[MSG_SIZ];
9605               snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9606               SendToProgram(buf, &second);
9607             }
9608         }
9609     } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9610         /* Kill off second chess program */
9611         if (second.isr != NULL)
9612           RemoveInputSource(second.isr);
9613         second.isr = NULL;
9614
9615         if (second.pr != NoProc) {
9616             DoSleep( appData.delayBeforeQuit );
9617             SendToProgram("quit\n", &second);
9618             DoSleep( appData.delayAfterQuit );
9619             DestroyChildProcess(second.pr, second.useSigterm);
9620         }
9621         second.pr = NoProc;
9622     }
9623
9624     if (matchMode && gameMode == TwoMachinesPlay) {
9625         switch (result) {
9626         case WhiteWins:
9627           if (first.twoMachinesColor[0] == 'w') {
9628             first.matchWins++;
9629           } else {
9630             second.matchWins++;
9631           }
9632           break;
9633         case BlackWins:
9634           if (first.twoMachinesColor[0] == 'b') {
9635             first.matchWins++;
9636           } else {
9637             second.matchWins++;
9638           }
9639           break;
9640         default:
9641           break;
9642         }
9643         if (matchGame < appData.matchGames) {
9644             char *tmp;
9645             if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
9646                 tmp = first.twoMachinesColor;
9647                 first.twoMachinesColor = second.twoMachinesColor;
9648                 second.twoMachinesColor = tmp;
9649             }
9650             gameMode = nextGameMode;
9651             matchGame++;
9652             if(appData.matchPause>10000 || appData.matchPause<10)
9653                 appData.matchPause = 10000; /* [HGM] make pause adjustable */
9654             ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
9655             endingGame = 0; /* [HGM] crash */
9656             return;
9657         } else {
9658             gameMode = nextGameMode;
9659             snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
9660                      first.tidy, second.tidy,
9661                      first.matchWins, second.matchWins,
9662                      appData.matchGames - (first.matchWins + second.matchWins));
9663             popupRequested++; // [HGM] crash: postpone to after resetting endingGame
9664             if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
9665                 first.twoMachinesColor = "black\n";
9666                 second.twoMachinesColor = "white\n";
9667             } else {
9668                 first.twoMachinesColor = "white\n";
9669                 second.twoMachinesColor = "black\n";
9670             }
9671         }
9672     }
9673     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
9674         !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
9675       ExitAnalyzeMode();
9676     gameMode = nextGameMode;
9677     ModeHighlight();
9678     endingGame = 0;  /* [HGM] crash */
9679     if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
9680       if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
9681         matchMode = FALSE; appData.matchGames = matchGame = 0;
9682         DisplayNote(buf);
9683       }
9684     }
9685 }
9686
9687 /* Assumes program was just initialized (initString sent).
9688    Leaves program in force mode. */
9689 void
9690 FeedMovesToProgram(cps, upto)
9691      ChessProgramState *cps;
9692      int upto;
9693 {
9694     int i;
9695
9696     if (appData.debugMode)
9697       fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
9698               startedFromSetupPosition ? "position and " : "",
9699               backwardMostMove, upto, cps->which);
9700     if(currentlyInitializedVariant != gameInfo.variant) {
9701       char buf[MSG_SIZ];
9702         // [HGM] variantswitch: make engine aware of new variant
9703         if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
9704                 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
9705         snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
9706         SendToProgram(buf, cps);
9707         currentlyInitializedVariant = gameInfo.variant;
9708     }
9709     SendToProgram("force\n", cps);
9710     if (startedFromSetupPosition) {
9711         SendBoard(cps, backwardMostMove);
9712     if (appData.debugMode) {
9713         fprintf(debugFP, "feedMoves\n");
9714     }
9715     }
9716     for (i = backwardMostMove; i < upto; i++) {
9717         SendMoveToProgram(i, cps);
9718     }
9719 }
9720
9721
9722 void
9723 ResurrectChessProgram()
9724 {
9725      /* The chess program may have exited.
9726         If so, restart it and feed it all the moves made so far. */
9727
9728     if (appData.noChessProgram || first.pr != NoProc) return;
9729
9730     StartChessProgram(&first);
9731     InitChessProgram(&first, FALSE);
9732     FeedMovesToProgram(&first, currentMove);
9733
9734     if (!first.sendTime) {
9735         /* can't tell gnuchess what its clock should read,
9736            so we bow to its notion. */
9737         ResetClocks();
9738         timeRemaining[0][currentMove] = whiteTimeRemaining;
9739         timeRemaining[1][currentMove] = blackTimeRemaining;
9740     }
9741
9742     if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
9743                 appData.icsEngineAnalyze) && first.analysisSupport) {
9744       SendToProgram("analyze\n", &first);
9745       first.analyzing = TRUE;
9746     }
9747 }
9748
9749 /*
9750  * Button procedures
9751  */
9752 void
9753 Reset(redraw, init)
9754      int redraw, init;
9755 {
9756     int i;
9757
9758     if (appData.debugMode) {
9759         fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
9760                 redraw, init, gameMode);
9761     }
9762     CleanupTail(); // [HGM] vari: delete any stored variations
9763     pausing = pauseExamInvalid = FALSE;
9764     startedFromSetupPosition = blackPlaysFirst = FALSE;
9765     firstMove = TRUE;
9766     whiteFlag = blackFlag = FALSE;
9767     userOfferedDraw = FALSE;
9768     hintRequested = bookRequested = FALSE;
9769     first.maybeThinking = FALSE;
9770     second.maybeThinking = FALSE;
9771     first.bookSuspend = FALSE; // [HGM] book
9772     second.bookSuspend = FALSE;
9773     thinkOutput[0] = NULLCHAR;
9774     lastHint[0] = NULLCHAR;
9775     ClearGameInfo(&gameInfo);
9776     gameInfo.variant = StringToVariant(appData.variant);
9777     ics_user_moved = ics_clock_paused = FALSE;
9778     ics_getting_history = H_FALSE;
9779     ics_gamenum = -1;
9780     white_holding[0] = black_holding[0] = NULLCHAR;
9781     ClearProgramStats();
9782     opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
9783
9784     ResetFrontEnd();
9785     ClearHighlights();
9786     flipView = appData.flipView;
9787     ClearPremoveHighlights();
9788     gotPremove = FALSE;
9789     alarmSounded = FALSE;
9790
9791     GameEnds(EndOfFile, NULL, GE_PLAYER);
9792     if(appData.serverMovesName != NULL) {
9793         /* [HGM] prepare to make moves file for broadcasting */
9794         clock_t t = clock();
9795         if(serverMoves != NULL) fclose(serverMoves);
9796         serverMoves = fopen(appData.serverMovesName, "r");
9797         if(serverMoves != NULL) {
9798             fclose(serverMoves);
9799             /* delay 15 sec before overwriting, so all clients can see end */
9800             while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
9801         }
9802         serverMoves = fopen(appData.serverMovesName, "w");
9803     }
9804
9805     ExitAnalyzeMode();
9806     gameMode = BeginningOfGame;
9807     ModeHighlight();
9808     if(appData.icsActive) gameInfo.variant = VariantNormal;
9809     currentMove = forwardMostMove = backwardMostMove = 0;
9810     InitPosition(redraw);
9811     for (i = 0; i < MAX_MOVES; i++) {
9812         if (commentList[i] != NULL) {
9813             free(commentList[i]);
9814             commentList[i] = NULL;
9815         }
9816     }
9817     ResetClocks();
9818     timeRemaining[0][0] = whiteTimeRemaining;
9819     timeRemaining[1][0] = blackTimeRemaining;
9820     if (first.pr == NULL) {
9821         StartChessProgram(&first);
9822     }
9823     if (init) {
9824             InitChessProgram(&first, startedFromSetupPosition);
9825     }
9826     DisplayTitle("");
9827     DisplayMessage("", "");
9828     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
9829     lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
9830 }
9831
9832 void
9833 AutoPlayGameLoop()
9834 {
9835     for (;;) {
9836         if (!AutoPlayOneMove())
9837           return;
9838         if (matchMode || appData.timeDelay == 0)
9839           continue;
9840         if (appData.timeDelay < 0)
9841           return;
9842         StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
9843         break;
9844     }
9845 }
9846
9847
9848 int
9849 AutoPlayOneMove()
9850 {
9851     int fromX, fromY, toX, toY;
9852
9853     if (appData.debugMode) {
9854       fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
9855     }
9856
9857     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
9858       return FALSE;
9859
9860     if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
9861       pvInfoList[currentMove].depth = programStats.depth;
9862       pvInfoList[currentMove].score = programStats.score;
9863       pvInfoList[currentMove].time  = 0;
9864       if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
9865     }
9866
9867     if (currentMove >= forwardMostMove) {
9868       if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
9869       gameMode = EditGame;
9870       ModeHighlight();
9871
9872       /* [AS] Clear current move marker at the end of a game */
9873       /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
9874
9875       return FALSE;
9876     }
9877
9878     toX = moveList[currentMove][2] - AAA;
9879     toY = moveList[currentMove][3] - ONE;
9880
9881     if (moveList[currentMove][1] == '@') {
9882         if (appData.highlightLastMove) {
9883             SetHighlights(-1, -1, toX, toY);
9884         }
9885     } else {
9886         fromX = moveList[currentMove][0] - AAA;
9887         fromY = moveList[currentMove][1] - ONE;
9888
9889         HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
9890
9891         AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
9892
9893         if (appData.highlightLastMove) {
9894             SetHighlights(fromX, fromY, toX, toY);
9895         }
9896     }
9897     DisplayMove(currentMove);
9898     SendMoveToProgram(currentMove++, &first);
9899     DisplayBothClocks();
9900     DrawPosition(FALSE, boards[currentMove]);
9901     // [HGM] PV info: always display, routine tests if empty
9902     DisplayComment(currentMove - 1, commentList[currentMove]);
9903     return TRUE;
9904 }
9905
9906
9907 int
9908 LoadGameOneMove(readAhead)
9909      ChessMove readAhead;
9910 {
9911     int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
9912     char promoChar = NULLCHAR;
9913     ChessMove moveType;
9914     char move[MSG_SIZ];
9915     char *p, *q;
9916
9917     if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
9918         gameMode != AnalyzeMode && gameMode != Training) {
9919         gameFileFP = NULL;
9920         return FALSE;
9921     }
9922
9923     yyboardindex = forwardMostMove;
9924     if (readAhead != EndOfFile) {
9925       moveType = readAhead;
9926     } else {
9927       if (gameFileFP == NULL)
9928           return FALSE;
9929       moveType = (ChessMove) Myylex();
9930     }
9931
9932     done = FALSE;
9933     switch (moveType) {
9934       case Comment:
9935         if (appData.debugMode)
9936           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
9937         p = yy_text;
9938
9939         /* append the comment but don't display it */
9940         AppendComment(currentMove, p, FALSE);
9941         return TRUE;
9942
9943       case WhiteCapturesEnPassant:
9944       case BlackCapturesEnPassant:
9945       case WhitePromotion:
9946       case BlackPromotion:
9947       case WhiteNonPromotion:
9948       case BlackNonPromotion:
9949       case NormalMove:
9950       case WhiteKingSideCastle:
9951       case WhiteQueenSideCastle:
9952       case BlackKingSideCastle:
9953       case BlackQueenSideCastle:
9954       case WhiteKingSideCastleWild:
9955       case WhiteQueenSideCastleWild:
9956       case BlackKingSideCastleWild:
9957       case BlackQueenSideCastleWild:
9958       /* PUSH Fabien */
9959       case WhiteHSideCastleFR:
9960       case WhiteASideCastleFR:
9961       case BlackHSideCastleFR:
9962       case BlackASideCastleFR:
9963       /* POP Fabien */
9964         if (appData.debugMode)
9965           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9966         fromX = currentMoveString[0] - AAA;
9967         fromY = currentMoveString[1] - ONE;
9968         toX = currentMoveString[2] - AAA;
9969         toY = currentMoveString[3] - ONE;
9970         promoChar = currentMoveString[4];
9971         break;
9972
9973       case WhiteDrop:
9974       case BlackDrop:
9975         if (appData.debugMode)
9976           fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
9977         fromX = moveType == WhiteDrop ?
9978           (int) CharToPiece(ToUpper(currentMoveString[0])) :
9979         (int) CharToPiece(ToLower(currentMoveString[0]));
9980         fromY = DROP_RANK;
9981         toX = currentMoveString[2] - AAA;
9982         toY = currentMoveString[3] - ONE;
9983         break;
9984
9985       case WhiteWins:
9986       case BlackWins:
9987       case GameIsDrawn:
9988       case GameUnfinished:
9989         if (appData.debugMode)
9990           fprintf(debugFP, "Parsed game end: %s\n", yy_text);
9991         p = strchr(yy_text, '{');
9992         if (p == NULL) p = strchr(yy_text, '(');
9993         if (p == NULL) {
9994             p = yy_text;
9995             if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
9996         } else {
9997             q = strchr(p, *p == '{' ? '}' : ')');
9998             if (q != NULL) *q = NULLCHAR;
9999             p++;
10000         }
10001         while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10002         GameEnds(moveType, p, GE_FILE);
10003         done = TRUE;
10004         if (cmailMsgLoaded) {
10005             ClearHighlights();
10006             flipView = WhiteOnMove(currentMove);
10007             if (moveType == GameUnfinished) flipView = !flipView;
10008             if (appData.debugMode)
10009               fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10010         }
10011         break;
10012
10013       case EndOfFile:
10014         if (appData.debugMode)
10015           fprintf(debugFP, "Parser hit end of file\n");
10016         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10017           case MT_NONE:
10018           case MT_CHECK:
10019             break;
10020           case MT_CHECKMATE:
10021           case MT_STAINMATE:
10022             if (WhiteOnMove(currentMove)) {
10023                 GameEnds(BlackWins, "Black mates", GE_FILE);
10024             } else {
10025                 GameEnds(WhiteWins, "White mates", GE_FILE);
10026             }
10027             break;
10028           case MT_STALEMATE:
10029             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10030             break;
10031         }
10032         done = TRUE;
10033         break;
10034
10035       case MoveNumberOne:
10036         if (lastLoadGameStart == GNUChessGame) {
10037             /* GNUChessGames have numbers, but they aren't move numbers */
10038             if (appData.debugMode)
10039               fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10040                       yy_text, (int) moveType);
10041             return LoadGameOneMove(EndOfFile); /* tail recursion */
10042         }
10043         /* else fall thru */
10044
10045       case XBoardGame:
10046       case GNUChessGame:
10047       case PGNTag:
10048         /* Reached start of next game in file */
10049         if (appData.debugMode)
10050           fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10051         switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10052           case MT_NONE:
10053           case MT_CHECK:
10054             break;
10055           case MT_CHECKMATE:
10056           case MT_STAINMATE:
10057             if (WhiteOnMove(currentMove)) {
10058                 GameEnds(BlackWins, "Black mates", GE_FILE);
10059             } else {
10060                 GameEnds(WhiteWins, "White mates", GE_FILE);
10061             }
10062             break;
10063           case MT_STALEMATE:
10064             GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10065             break;
10066         }
10067         done = TRUE;
10068         break;
10069
10070       case PositionDiagram:     /* should not happen; ignore */
10071       case ElapsedTime:         /* ignore */
10072       case NAG:                 /* ignore */
10073         if (appData.debugMode)
10074           fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10075                   yy_text, (int) moveType);
10076         return LoadGameOneMove(EndOfFile); /* tail recursion */
10077
10078       case IllegalMove:
10079         if (appData.testLegality) {
10080             if (appData.debugMode)
10081               fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10082             snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10083                     (forwardMostMove / 2) + 1,
10084                     WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10085             DisplayError(move, 0);
10086             done = TRUE;
10087         } else {
10088             if (appData.debugMode)
10089               fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10090                       yy_text, currentMoveString);
10091             fromX = currentMoveString[0] - AAA;
10092             fromY = currentMoveString[1] - ONE;
10093             toX = currentMoveString[2] - AAA;
10094             toY = currentMoveString[3] - ONE;
10095             promoChar = currentMoveString[4];
10096         }
10097         break;
10098
10099       case AmbiguousMove:
10100         if (appData.debugMode)
10101           fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10102         snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10103                 (forwardMostMove / 2) + 1,
10104                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10105         DisplayError(move, 0);
10106         done = TRUE;
10107         break;
10108
10109       default:
10110       case ImpossibleMove:
10111         if (appData.debugMode)
10112           fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10113         snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10114                 (forwardMostMove / 2) + 1,
10115                 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10116         DisplayError(move, 0);
10117         done = TRUE;
10118         break;
10119     }
10120
10121     if (done) {
10122         if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10123             DrawPosition(FALSE, boards[currentMove]);
10124             DisplayBothClocks();
10125             if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10126               DisplayComment(currentMove - 1, commentList[currentMove]);
10127         }
10128         (void) StopLoadGameTimer();
10129         gameFileFP = NULL;
10130         cmailOldMove = forwardMostMove;
10131         return FALSE;
10132     } else {
10133         /* currentMoveString is set as a side-effect of yylex */
10134
10135         thinkOutput[0] = NULLCHAR;
10136         MakeMove(fromX, fromY, toX, toY, promoChar);
10137         currentMove = forwardMostMove;
10138         return TRUE;
10139     }
10140 }
10141
10142 /* Load the nth game from the given file */
10143 int
10144 LoadGameFromFile(filename, n, title, useList)
10145      char *filename;
10146      int n;
10147      char *title;
10148      /*Boolean*/ int useList;
10149 {
10150     FILE *f;
10151     char buf[MSG_SIZ];
10152
10153     if (strcmp(filename, "-") == 0) {
10154         f = stdin;
10155         title = "stdin";
10156     } else {
10157         f = fopen(filename, "rb");
10158         if (f == NULL) {
10159           snprintf(buf, sizeof(buf),  _("Can't open \"%s\""), filename);
10160             DisplayError(buf, errno);
10161             return FALSE;
10162         }
10163     }
10164     if (fseek(f, 0, 0) == -1) {
10165         /* f is not seekable; probably a pipe */
10166         useList = FALSE;
10167     }
10168     if (useList && n == 0) {
10169         int error = GameListBuild(f);
10170         if (error) {
10171             DisplayError(_("Cannot build game list"), error);
10172         } else if (!ListEmpty(&gameList) &&
10173                    ((ListGame *) gameList.tailPred)->number > 1) {
10174             GameListPopUp(f, title);
10175             return TRUE;
10176         }
10177         GameListDestroy();
10178         n = 1;
10179     }
10180     if (n == 0) n = 1;
10181     return LoadGame(f, n, title, FALSE);
10182 }
10183
10184
10185 void
10186 MakeRegisteredMove()
10187 {
10188     int fromX, fromY, toX, toY;
10189     char promoChar;
10190     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10191         switch (cmailMoveType[lastLoadGameNumber - 1]) {
10192           case CMAIL_MOVE:
10193           case CMAIL_DRAW:
10194             if (appData.debugMode)
10195               fprintf(debugFP, "Restoring %s for game %d\n",
10196                       cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10197
10198             thinkOutput[0] = NULLCHAR;
10199             safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10200             fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10201             fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10202             toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10203             toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10204             promoChar = cmailMove[lastLoadGameNumber - 1][4];
10205             MakeMove(fromX, fromY, toX, toY, promoChar);
10206             ShowMove(fromX, fromY, toX, toY);
10207
10208             switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10209               case MT_NONE:
10210               case MT_CHECK:
10211                 break;
10212
10213               case MT_CHECKMATE:
10214               case MT_STAINMATE:
10215                 if (WhiteOnMove(currentMove)) {
10216                     GameEnds(BlackWins, "Black mates", GE_PLAYER);
10217                 } else {
10218                     GameEnds(WhiteWins, "White mates", GE_PLAYER);
10219                 }
10220                 break;
10221
10222               case MT_STALEMATE:
10223                 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10224                 break;
10225             }
10226
10227             break;
10228
10229           case CMAIL_RESIGN:
10230             if (WhiteOnMove(currentMove)) {
10231                 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10232             } else {
10233                 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10234             }
10235             break;
10236
10237           case CMAIL_ACCEPT:
10238             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10239             break;
10240
10241           default:
10242             break;
10243         }
10244     }
10245
10246     return;
10247 }
10248
10249 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10250 int
10251 CmailLoadGame(f, gameNumber, title, useList)
10252      FILE *f;
10253      int gameNumber;
10254      char *title;
10255      int useList;
10256 {
10257     int retVal;
10258
10259     if (gameNumber > nCmailGames) {
10260         DisplayError(_("No more games in this message"), 0);
10261         return FALSE;
10262     }
10263     if (f == lastLoadGameFP) {
10264         int offset = gameNumber - lastLoadGameNumber;
10265         if (offset == 0) {
10266             cmailMsg[0] = NULLCHAR;
10267             if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10268                 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10269                 nCmailMovesRegistered--;
10270             }
10271             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10272             if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10273                 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10274             }
10275         } else {
10276             if (! RegisterMove()) return FALSE;
10277         }
10278     }
10279
10280     retVal = LoadGame(f, gameNumber, title, useList);
10281
10282     /* Make move registered during previous look at this game, if any */
10283     MakeRegisteredMove();
10284
10285     if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10286         commentList[currentMove]
10287           = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10288         DisplayComment(currentMove - 1, commentList[currentMove]);
10289     }
10290
10291     return retVal;
10292 }
10293
10294 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10295 int
10296 ReloadGame(offset)
10297      int offset;
10298 {
10299     int gameNumber = lastLoadGameNumber + offset;
10300     if (lastLoadGameFP == NULL) {
10301         DisplayError(_("No game has been loaded yet"), 0);
10302         return FALSE;
10303     }
10304     if (gameNumber <= 0) {
10305         DisplayError(_("Can't back up any further"), 0);
10306         return FALSE;
10307     }
10308     if (cmailMsgLoaded) {
10309         return CmailLoadGame(lastLoadGameFP, gameNumber,
10310                              lastLoadGameTitle, lastLoadGameUseList);
10311     } else {
10312         return LoadGame(lastLoadGameFP, gameNumber,
10313                         lastLoadGameTitle, lastLoadGameUseList);
10314     }
10315 }
10316
10317
10318
10319 /* Load the nth game from open file f */
10320 int
10321 LoadGame(f, gameNumber, title, useList)
10322      FILE *f;
10323      int gameNumber;
10324      char *title;
10325      int useList;
10326 {
10327     ChessMove cm;
10328     char buf[MSG_SIZ];
10329     int gn = gameNumber;
10330     ListGame *lg = NULL;
10331     int numPGNTags = 0;
10332     int err;
10333     GameMode oldGameMode;
10334     VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10335
10336     if (appData.debugMode)
10337         fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10338
10339     if (gameMode == Training )
10340         SetTrainingModeOff();
10341
10342     oldGameMode = gameMode;
10343     if (gameMode != BeginningOfGame) {
10344       Reset(FALSE, TRUE);
10345     }
10346
10347     gameFileFP = f;
10348     if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10349         fclose(lastLoadGameFP);
10350     }
10351
10352     if (useList) {
10353         lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10354
10355         if (lg) {
10356             fseek(f, lg->offset, 0);
10357             GameListHighlight(gameNumber);
10358             gn = 1;
10359         }
10360         else {
10361             DisplayError(_("Game number out of range"), 0);
10362             return FALSE;
10363         }
10364     } else {
10365         GameListDestroy();
10366         if (fseek(f, 0, 0) == -1) {
10367             if (f == lastLoadGameFP ?
10368                 gameNumber == lastLoadGameNumber + 1 :
10369                 gameNumber == 1) {
10370                 gn = 1;
10371             } else {
10372                 DisplayError(_("Can't seek on game file"), 0);
10373                 return FALSE;
10374             }
10375         }
10376     }
10377     lastLoadGameFP = f;
10378     lastLoadGameNumber = gameNumber;
10379     safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10380     lastLoadGameUseList = useList;
10381
10382     yynewfile(f);
10383
10384     if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10385       snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10386                 lg->gameInfo.black);
10387             DisplayTitle(buf);
10388     } else if (*title != NULLCHAR) {
10389         if (gameNumber > 1) {
10390           snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10391             DisplayTitle(buf);
10392         } else {
10393             DisplayTitle(title);
10394         }
10395     }
10396
10397     if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10398         gameMode = PlayFromGameFile;
10399         ModeHighlight();
10400     }
10401
10402     currentMove = forwardMostMove = backwardMostMove = 0;
10403     CopyBoard(boards[0], initialPosition);
10404     StopClocks();
10405
10406     /*
10407      * Skip the first gn-1 games in the file.
10408      * Also skip over anything that precedes an identifiable
10409      * start of game marker, to avoid being confused by
10410      * garbage at the start of the file.  Currently
10411      * recognized start of game markers are the move number "1",
10412      * the pattern "gnuchess .* game", the pattern
10413      * "^[#;%] [^ ]* game file", and a PGN tag block.
10414      * A game that starts with one of the latter two patterns
10415      * will also have a move number 1, possibly
10416      * following a position diagram.
10417      * 5-4-02: Let's try being more lenient and allowing a game to
10418      * start with an unnumbered move.  Does that break anything?
10419      */
10420     cm = lastLoadGameStart = EndOfFile;
10421     while (gn > 0) {
10422         yyboardindex = forwardMostMove;
10423         cm = (ChessMove) Myylex();
10424         switch (cm) {
10425           case EndOfFile:
10426             if (cmailMsgLoaded) {
10427                 nCmailGames = CMAIL_MAX_GAMES - gn;
10428             } else {
10429                 Reset(TRUE, TRUE);
10430                 DisplayError(_("Game not found in file"), 0);
10431             }
10432             return FALSE;
10433
10434           case GNUChessGame:
10435           case XBoardGame:
10436             gn--;
10437             lastLoadGameStart = cm;
10438             break;
10439
10440           case MoveNumberOne:
10441             switch (lastLoadGameStart) {
10442               case GNUChessGame:
10443               case XBoardGame:
10444               case PGNTag:
10445                 break;
10446               case MoveNumberOne:
10447               case EndOfFile:
10448                 gn--;           /* count this game */
10449                 lastLoadGameStart = cm;
10450                 break;
10451               default:
10452                 /* impossible */
10453                 break;
10454             }
10455             break;
10456
10457           case PGNTag:
10458             switch (lastLoadGameStart) {
10459               case GNUChessGame:
10460               case PGNTag:
10461               case MoveNumberOne:
10462               case EndOfFile:
10463                 gn--;           /* count this game */
10464                 lastLoadGameStart = cm;
10465                 break;
10466               case XBoardGame:
10467                 lastLoadGameStart = cm; /* game counted already */
10468                 break;
10469               default:
10470                 /* impossible */
10471                 break;
10472             }
10473             if (gn > 0) {
10474                 do {
10475                     yyboardindex = forwardMostMove;
10476                     cm = (ChessMove) Myylex();
10477                 } while (cm == PGNTag || cm == Comment);
10478             }
10479             break;
10480
10481           case WhiteWins:
10482           case BlackWins:
10483           case GameIsDrawn:
10484             if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10485                 if (   cmailResult[CMAIL_MAX_GAMES - gn - 1]
10486                     != CMAIL_OLD_RESULT) {
10487                     nCmailResults ++ ;
10488                     cmailResult[  CMAIL_MAX_GAMES
10489                                 - gn - 1] = CMAIL_OLD_RESULT;
10490                 }
10491             }
10492             break;
10493
10494           case NormalMove:
10495             /* Only a NormalMove can be at the start of a game
10496              * without a position diagram. */
10497             if (lastLoadGameStart == EndOfFile ) {
10498               gn--;
10499               lastLoadGameStart = MoveNumberOne;
10500             }
10501             break;
10502
10503           default:
10504             break;
10505         }
10506     }
10507
10508     if (appData.debugMode)
10509       fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10510
10511     if (cm == XBoardGame) {
10512         /* Skip any header junk before position diagram and/or move 1 */
10513         for (;;) {
10514             yyboardindex = forwardMostMove;
10515             cm = (ChessMove) Myylex();
10516
10517             if (cm == EndOfFile ||
10518                 cm == GNUChessGame || cm == XBoardGame) {
10519                 /* Empty game; pretend end-of-file and handle later */
10520                 cm = EndOfFile;
10521                 break;
10522             }
10523
10524             if (cm == MoveNumberOne || cm == PositionDiagram ||
10525                 cm == PGNTag || cm == Comment)
10526               break;
10527         }
10528     } else if (cm == GNUChessGame) {
10529         if (gameInfo.event != NULL) {
10530             free(gameInfo.event);
10531         }
10532         gameInfo.event = StrSave(yy_text);
10533     }
10534
10535     startedFromSetupPosition = FALSE;
10536     while (cm == PGNTag) {
10537         if (appData.debugMode)
10538           fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10539         err = ParsePGNTag(yy_text, &gameInfo);
10540         if (!err) numPGNTags++;
10541
10542         /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10543         if(gameInfo.variant != oldVariant) {
10544             startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10545             ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10546             InitPosition(TRUE);
10547             oldVariant = gameInfo.variant;
10548             if (appData.debugMode)
10549               fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10550         }
10551
10552
10553         if (gameInfo.fen != NULL) {
10554           Board initial_position;
10555           startedFromSetupPosition = TRUE;
10556           if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10557             Reset(TRUE, TRUE);
10558             DisplayError(_("Bad FEN position in file"), 0);
10559             return FALSE;
10560           }
10561           CopyBoard(boards[0], initial_position);
10562           if (blackPlaysFirst) {
10563             currentMove = forwardMostMove = backwardMostMove = 1;
10564             CopyBoard(boards[1], initial_position);
10565             safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10566             safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10567             timeRemaining[0][1] = whiteTimeRemaining;
10568             timeRemaining[1][1] = blackTimeRemaining;
10569             if (commentList[0] != NULL) {
10570               commentList[1] = commentList[0];
10571               commentList[0] = NULL;
10572             }
10573           } else {
10574             currentMove = forwardMostMove = backwardMostMove = 0;
10575           }
10576           /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10577           {   int i;
10578               initialRulePlies = FENrulePlies;
10579               for( i=0; i< nrCastlingRights; i++ )
10580                   initialRights[i] = initial_position[CASTLING][i];
10581           }
10582           yyboardindex = forwardMostMove;
10583           free(gameInfo.fen);
10584           gameInfo.fen = NULL;
10585         }
10586
10587         yyboardindex = forwardMostMove;
10588         cm = (ChessMove) Myylex();
10589
10590         /* Handle comments interspersed among the tags */
10591         while (cm == Comment) {
10592             char *p;
10593             if (appData.debugMode)
10594               fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10595             p = yy_text;
10596             AppendComment(currentMove, p, FALSE);
10597             yyboardindex = forwardMostMove;
10598             cm = (ChessMove) Myylex();
10599         }
10600     }
10601
10602     /* don't rely on existence of Event tag since if game was
10603      * pasted from clipboard the Event tag may not exist
10604      */
10605     if (numPGNTags > 0){
10606         char *tags;
10607         if (gameInfo.variant == VariantNormal) {
10608           VariantClass v = StringToVariant(gameInfo.event);
10609           // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
10610           if(v < VariantShogi) gameInfo.variant = v;
10611         }
10612         if (!matchMode) {
10613           if( appData.autoDisplayTags ) {
10614             tags = PGNTags(&gameInfo);
10615             TagsPopUp(tags, CmailMsg());
10616             free(tags);
10617           }
10618         }
10619     } else {
10620         /* Make something up, but don't display it now */
10621         SetGameInfo();
10622         TagsPopDown();
10623     }
10624
10625     if (cm == PositionDiagram) {
10626         int i, j;
10627         char *p;
10628         Board initial_position;
10629
10630         if (appData.debugMode)
10631           fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
10632
10633         if (!startedFromSetupPosition) {
10634             p = yy_text;
10635             for (i = BOARD_HEIGHT - 1; i >= 0; i--)
10636               for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
10637                 switch (*p) {
10638                   case '{':
10639                   case '[':
10640                   case '-':
10641                   case ' ':
10642                   case '\t':
10643                   case '\n':
10644                   case '\r':
10645                     break;
10646                   default:
10647                     initial_position[i][j++] = CharToPiece(*p);
10648                     break;
10649                 }
10650             while (*p == ' ' || *p == '\t' ||
10651                    *p == '\n' || *p == '\r') p++;
10652
10653             if (strncmp(p, "black", strlen("black"))==0)
10654               blackPlaysFirst = TRUE;
10655             else
10656               blackPlaysFirst = FALSE;
10657             startedFromSetupPosition = TRUE;
10658
10659             CopyBoard(boards[0], initial_position);
10660             if (blackPlaysFirst) {
10661                 currentMove = forwardMostMove = backwardMostMove = 1;
10662                 CopyBoard(boards[1], initial_position);
10663                 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10664                 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10665                 timeRemaining[0][1] = whiteTimeRemaining;
10666                 timeRemaining[1][1] = blackTimeRemaining;
10667                 if (commentList[0] != NULL) {
10668                     commentList[1] = commentList[0];
10669                     commentList[0] = NULL;
10670                 }
10671             } else {
10672                 currentMove = forwardMostMove = backwardMostMove = 0;
10673             }
10674         }
10675         yyboardindex = forwardMostMove;
10676         cm = (ChessMove) Myylex();
10677     }
10678
10679     if (first.pr == NoProc) {
10680         StartChessProgram(&first);
10681     }
10682     InitChessProgram(&first, FALSE);
10683     SendToProgram("force\n", &first);
10684     if (startedFromSetupPosition) {
10685         SendBoard(&first, forwardMostMove);
10686     if (appData.debugMode) {
10687         fprintf(debugFP, "Load Game\n");
10688     }
10689         DisplayBothClocks();
10690     }
10691
10692     /* [HGM] server: flag to write setup moves in broadcast file as one */
10693     loadFlag = appData.suppressLoadMoves;
10694
10695     while (cm == Comment) {
10696         char *p;
10697         if (appData.debugMode)
10698           fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10699         p = yy_text;
10700         AppendComment(currentMove, p, FALSE);
10701         yyboardindex = forwardMostMove;
10702         cm = (ChessMove) Myylex();
10703     }
10704
10705     if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
10706         cm == WhiteWins || cm == BlackWins ||
10707         cm == GameIsDrawn || cm == GameUnfinished) {
10708         DisplayMessage("", _("No moves in game"));
10709         if (cmailMsgLoaded) {
10710             if (appData.debugMode)
10711               fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
10712             ClearHighlights();
10713             flipView = FALSE;
10714         }
10715         DrawPosition(FALSE, boards[currentMove]);
10716         DisplayBothClocks();
10717         gameMode = EditGame;
10718         ModeHighlight();
10719         gameFileFP = NULL;
10720         cmailOldMove = 0;
10721         return TRUE;
10722     }
10723
10724     // [HGM] PV info: routine tests if comment empty
10725     if (!matchMode && (pausing || appData.timeDelay != 0)) {
10726         DisplayComment(currentMove - 1, commentList[currentMove]);
10727     }
10728     if (!matchMode && appData.timeDelay != 0)
10729       DrawPosition(FALSE, boards[currentMove]);
10730
10731     if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
10732       programStats.ok_to_send = 1;
10733     }
10734
10735     /* if the first token after the PGN tags is a move
10736      * and not move number 1, retrieve it from the parser
10737      */
10738     if (cm != MoveNumberOne)
10739         LoadGameOneMove(cm);
10740
10741     /* load the remaining moves from the file */
10742     while (LoadGameOneMove(EndOfFile)) {
10743       timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
10744       timeRemaining[1][forwardMostMove] = blackTimeRemaining;
10745     }
10746
10747     /* rewind to the start of the game */
10748     currentMove = backwardMostMove;
10749
10750     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10751
10752     if (oldGameMode == AnalyzeFile ||
10753         oldGameMode == AnalyzeMode) {
10754       AnalyzeFileEvent();
10755     }
10756
10757     if (matchMode || appData.timeDelay == 0) {
10758       ToEndEvent();
10759       gameMode = EditGame;
10760       ModeHighlight();
10761     } else if (appData.timeDelay > 0) {
10762       AutoPlayGameLoop();
10763     }
10764
10765     if (appData.debugMode)
10766         fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
10767
10768     loadFlag = 0; /* [HGM] true game starts */
10769     return TRUE;
10770 }
10771
10772 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
10773 int
10774 ReloadPosition(offset)
10775      int offset;
10776 {
10777     int positionNumber = lastLoadPositionNumber + offset;
10778     if (lastLoadPositionFP == NULL) {
10779         DisplayError(_("No position has been loaded yet"), 0);
10780         return FALSE;
10781     }
10782     if (positionNumber <= 0) {
10783         DisplayError(_("Can't back up any further"), 0);
10784         return FALSE;
10785     }
10786     return LoadPosition(lastLoadPositionFP, positionNumber,
10787                         lastLoadPositionTitle);
10788 }
10789
10790 /* Load the nth position from the given file */
10791 int
10792 LoadPositionFromFile(filename, n, title)
10793      char *filename;
10794      int n;
10795      char *title;
10796 {
10797     FILE *f;
10798     char buf[MSG_SIZ];
10799
10800     if (strcmp(filename, "-") == 0) {
10801         return LoadPosition(stdin, n, "stdin");
10802     } else {
10803         f = fopen(filename, "rb");
10804         if (f == NULL) {
10805             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10806             DisplayError(buf, errno);
10807             return FALSE;
10808         } else {
10809             return LoadPosition(f, n, title);
10810         }
10811     }
10812 }
10813
10814 /* Load the nth position from the given open file, and close it */
10815 int
10816 LoadPosition(f, positionNumber, title)
10817      FILE *f;
10818      int positionNumber;
10819      char *title;
10820 {
10821     char *p, line[MSG_SIZ];
10822     Board initial_position;
10823     int i, j, fenMode, pn;
10824
10825     if (gameMode == Training )
10826         SetTrainingModeOff();
10827
10828     if (gameMode != BeginningOfGame) {
10829         Reset(FALSE, TRUE);
10830     }
10831     if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
10832         fclose(lastLoadPositionFP);
10833     }
10834     if (positionNumber == 0) positionNumber = 1;
10835     lastLoadPositionFP = f;
10836     lastLoadPositionNumber = positionNumber;
10837     safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
10838     if (first.pr == NoProc) {
10839       StartChessProgram(&first);
10840       InitChessProgram(&first, FALSE);
10841     }
10842     pn = positionNumber;
10843     if (positionNumber < 0) {
10844         /* Negative position number means to seek to that byte offset */
10845         if (fseek(f, -positionNumber, 0) == -1) {
10846             DisplayError(_("Can't seek on position file"), 0);
10847             return FALSE;
10848         };
10849         pn = 1;
10850     } else {
10851         if (fseek(f, 0, 0) == -1) {
10852             if (f == lastLoadPositionFP ?
10853                 positionNumber == lastLoadPositionNumber + 1 :
10854                 positionNumber == 1) {
10855                 pn = 1;
10856             } else {
10857                 DisplayError(_("Can't seek on position file"), 0);
10858                 return FALSE;
10859             }
10860         }
10861     }
10862     /* See if this file is FEN or old-style xboard */
10863     if (fgets(line, MSG_SIZ, f) == NULL) {
10864         DisplayError(_("Position not found in file"), 0);
10865         return FALSE;
10866     }
10867     // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
10868     fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
10869
10870     if (pn >= 2) {
10871         if (fenMode || line[0] == '#') pn--;
10872         while (pn > 0) {
10873             /* skip positions before number pn */
10874             if (fgets(line, MSG_SIZ, f) == NULL) {
10875                 Reset(TRUE, TRUE);
10876                 DisplayError(_("Position not found in file"), 0);
10877                 return FALSE;
10878             }
10879             if (fenMode || line[0] == '#') pn--;
10880         }
10881     }
10882
10883     if (fenMode) {
10884         if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
10885             DisplayError(_("Bad FEN position in file"), 0);
10886             return FALSE;
10887         }
10888     } else {
10889         (void) fgets(line, MSG_SIZ, f);
10890         (void) fgets(line, MSG_SIZ, f);
10891
10892         for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
10893             (void) fgets(line, MSG_SIZ, f);
10894             for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
10895                 if (*p == ' ')
10896                   continue;
10897                 initial_position[i][j++] = CharToPiece(*p);
10898             }
10899         }
10900
10901         blackPlaysFirst = FALSE;
10902         if (!feof(f)) {
10903             (void) fgets(line, MSG_SIZ, f);
10904             if (strncmp(line, "black", strlen("black"))==0)
10905               blackPlaysFirst = TRUE;
10906         }
10907     }
10908     startedFromSetupPosition = TRUE;
10909
10910     SendToProgram("force\n", &first);
10911     CopyBoard(boards[0], initial_position);
10912     if (blackPlaysFirst) {
10913         currentMove = forwardMostMove = backwardMostMove = 1;
10914         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10915         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10916         CopyBoard(boards[1], initial_position);
10917         DisplayMessage("", _("Black to play"));
10918     } else {
10919         currentMove = forwardMostMove = backwardMostMove = 0;
10920         DisplayMessage("", _("White to play"));
10921     }
10922     initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
10923     SendBoard(&first, forwardMostMove);
10924     if (appData.debugMode) {
10925 int i, j;
10926   for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
10927   for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
10928         fprintf(debugFP, "Load Position\n");
10929     }
10930
10931     if (positionNumber > 1) {
10932       snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
10933         DisplayTitle(line);
10934     } else {
10935         DisplayTitle(title);
10936     }
10937     gameMode = EditGame;
10938     ModeHighlight();
10939     ResetClocks();
10940     timeRemaining[0][1] = whiteTimeRemaining;
10941     timeRemaining[1][1] = blackTimeRemaining;
10942     DrawPosition(FALSE, boards[currentMove]);
10943
10944     return TRUE;
10945 }
10946
10947
10948 void
10949 CopyPlayerNameIntoFileName(dest, src)
10950      char **dest, *src;
10951 {
10952     while (*src != NULLCHAR && *src != ',') {
10953         if (*src == ' ') {
10954             *(*dest)++ = '_';
10955             src++;
10956         } else {
10957             *(*dest)++ = *src++;
10958         }
10959     }
10960 }
10961
10962 char *DefaultFileName(ext)
10963      char *ext;
10964 {
10965     static char def[MSG_SIZ];
10966     char *p;
10967
10968     if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
10969         p = def;
10970         CopyPlayerNameIntoFileName(&p, gameInfo.white);
10971         *p++ = '-';
10972         CopyPlayerNameIntoFileName(&p, gameInfo.black);
10973         *p++ = '.';
10974         safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
10975     } else {
10976         def[0] = NULLCHAR;
10977     }
10978     return def;
10979 }
10980
10981 /* Save the current game to the given file */
10982 int
10983 SaveGameToFile(filename, append)
10984      char *filename;
10985      int append;
10986 {
10987     FILE *f;
10988     char buf[MSG_SIZ];
10989     int result;
10990
10991     if (strcmp(filename, "-") == 0) {
10992         return SaveGame(stdout, 0, NULL);
10993     } else {
10994         f = fopen(filename, append ? "a" : "w");
10995         if (f == NULL) {
10996             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10997             DisplayError(buf, errno);
10998             return FALSE;
10999         } else {
11000             safeStrCpy(buf, lastMsg, MSG_SIZ);
11001             DisplayMessage(_("Waiting for access to save file"), "");
11002             flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11003             DisplayMessage(_("Saving game"), "");
11004             if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno);     // better safe than sorry...
11005             result = SaveGame(f, 0, NULL);
11006             DisplayMessage(buf, "");
11007             return result;
11008         }
11009     }
11010 }
11011
11012 char *
11013 SavePart(str)
11014      char *str;
11015 {
11016     static char buf[MSG_SIZ];
11017     char *p;
11018
11019     p = strchr(str, ' ');
11020     if (p == NULL) return str;
11021     strncpy(buf, str, p - str);
11022     buf[p - str] = NULLCHAR;
11023     return buf;
11024 }
11025
11026 #define PGN_MAX_LINE 75
11027
11028 #define PGN_SIDE_WHITE  0
11029 #define PGN_SIDE_BLACK  1
11030
11031 /* [AS] */
11032 static int FindFirstMoveOutOfBook( int side )
11033 {
11034     int result = -1;
11035
11036     if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11037         int index = backwardMostMove;
11038         int has_book_hit = 0;
11039
11040         if( (index % 2) != side ) {
11041             index++;
11042         }
11043
11044         while( index < forwardMostMove ) {
11045             /* Check to see if engine is in book */
11046             int depth = pvInfoList[index].depth;
11047             int score = pvInfoList[index].score;
11048             int in_book = 0;
11049
11050             if( depth <= 2 ) {
11051                 in_book = 1;
11052             }
11053             else if( score == 0 && depth == 63 ) {
11054                 in_book = 1; /* Zappa */
11055             }
11056             else if( score == 2 && depth == 99 ) {
11057                 in_book = 1; /* Abrok */
11058             }
11059
11060             has_book_hit += in_book;
11061
11062             if( ! in_book ) {
11063                 result = index;
11064
11065                 break;
11066             }
11067
11068             index += 2;
11069         }
11070     }
11071
11072     return result;
11073 }
11074
11075 /* [AS] */
11076 void GetOutOfBookInfo( char * buf )
11077 {
11078     int oob[2];
11079     int i;
11080     int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11081
11082     oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11083     oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11084
11085     *buf = '\0';
11086
11087     if( oob[0] >= 0 || oob[1] >= 0 ) {
11088         for( i=0; i<2; i++ ) {
11089             int idx = oob[i];
11090
11091             if( idx >= 0 ) {
11092                 if( i > 0 && oob[0] >= 0 ) {
11093                     strcat( buf, "   " );
11094                 }
11095
11096                 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11097                 sprintf( buf+strlen(buf), "%s%.2f",
11098                     pvInfoList[idx].score >= 0 ? "+" : "",
11099                     pvInfoList[idx].score / 100.0 );
11100             }
11101         }
11102     }
11103 }
11104
11105 /* Save game in PGN style and close the file */
11106 int
11107 SaveGamePGN(f)
11108      FILE *f;
11109 {
11110     int i, offset, linelen, newblock;
11111     time_t tm;
11112 //    char *movetext;
11113     char numtext[32];
11114     int movelen, numlen, blank;
11115     char move_buffer[100]; /* [AS] Buffer for move+PV info */
11116
11117     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11118
11119     tm = time((time_t *) NULL);
11120
11121     PrintPGNTags(f, &gameInfo);
11122
11123     if (backwardMostMove > 0 || startedFromSetupPosition) {
11124         char *fen = PositionToFEN(backwardMostMove, NULL);
11125         fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11126         fprintf(f, "\n{--------------\n");
11127         PrintPosition(f, backwardMostMove);
11128         fprintf(f, "--------------}\n");
11129         free(fen);
11130     }
11131     else {
11132         /* [AS] Out of book annotation */
11133         if( appData.saveOutOfBookInfo ) {
11134             char buf[64];
11135
11136             GetOutOfBookInfo( buf );
11137
11138             if( buf[0] != '\0' ) {
11139                 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11140             }
11141         }
11142
11143         fprintf(f, "\n");
11144     }
11145
11146     i = backwardMostMove;
11147     linelen = 0;
11148     newblock = TRUE;
11149
11150     while (i < forwardMostMove) {
11151         /* Print comments preceding this move */
11152         if (commentList[i] != NULL) {
11153             if (linelen > 0) fprintf(f, "\n");
11154             fprintf(f, "%s", commentList[i]);
11155             linelen = 0;
11156             newblock = TRUE;
11157         }
11158
11159         /* Format move number */
11160         if ((i % 2) == 0)
11161           snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11162         else
11163           if (newblock)
11164             snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11165           else
11166             numtext[0] = NULLCHAR;
11167
11168         numlen = strlen(numtext);
11169         newblock = FALSE;
11170
11171         /* Print move number */
11172         blank = linelen > 0 && numlen > 0;
11173         if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11174             fprintf(f, "\n");
11175             linelen = 0;
11176             blank = 0;
11177         }
11178         if (blank) {
11179             fprintf(f, " ");
11180             linelen++;
11181         }
11182         fprintf(f, "%s", numtext);
11183         linelen += numlen;
11184
11185         /* Get move */
11186         safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11187         movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11188
11189         /* Print move */
11190         blank = linelen > 0 && movelen > 0;
11191         if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11192             fprintf(f, "\n");
11193             linelen = 0;
11194             blank = 0;
11195         }
11196         if (blank) {
11197             fprintf(f, " ");
11198             linelen++;
11199         }
11200         fprintf(f, "%s", move_buffer);
11201         linelen += movelen;
11202
11203         /* [AS] Add PV info if present */
11204         if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11205             /* [HGM] add time */
11206             char buf[MSG_SIZ]; int seconds;
11207
11208             seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11209
11210             if( seconds <= 0)
11211               buf[0] = 0;
11212             else
11213               if( seconds < 30 )
11214                 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11215               else
11216                 {
11217                   seconds = (seconds + 4)/10; // round to full seconds
11218                   if( seconds < 60 )
11219                     snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11220                   else
11221                     snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11222                 }
11223
11224             snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11225                       pvInfoList[i].score >= 0 ? "+" : "",
11226                       pvInfoList[i].score / 100.0,
11227                       pvInfoList[i].depth,
11228                       buf );
11229
11230             movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11231
11232             /* Print score/depth */
11233             blank = linelen > 0 && movelen > 0;
11234             if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11235                 fprintf(f, "\n");
11236                 linelen = 0;
11237                 blank = 0;
11238             }
11239             if (blank) {
11240                 fprintf(f, " ");
11241                 linelen++;
11242             }
11243             fprintf(f, "%s", move_buffer);
11244             linelen += movelen;
11245         }
11246
11247         i++;
11248     }
11249
11250     /* Start a new line */
11251     if (linelen > 0) fprintf(f, "\n");
11252
11253     /* Print comments after last move */
11254     if (commentList[i] != NULL) {
11255         fprintf(f, "%s\n", commentList[i]);
11256     }
11257
11258     /* Print result */
11259     if (gameInfo.resultDetails != NULL &&
11260         gameInfo.resultDetails[0] != NULLCHAR) {
11261         fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11262                 PGNResult(gameInfo.result));
11263     } else {
11264         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11265     }
11266
11267     fclose(f);
11268     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11269     return TRUE;
11270 }
11271
11272 /* Save game in old style and close the file */
11273 int
11274 SaveGameOldStyle(f)
11275      FILE *f;
11276 {
11277     int i, offset;
11278     time_t tm;
11279
11280     tm = time((time_t *) NULL);
11281
11282     fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11283     PrintOpponents(f);
11284
11285     if (backwardMostMove > 0 || startedFromSetupPosition) {
11286         fprintf(f, "\n[--------------\n");
11287         PrintPosition(f, backwardMostMove);
11288         fprintf(f, "--------------]\n");
11289     } else {
11290         fprintf(f, "\n");
11291     }
11292
11293     i = backwardMostMove;
11294     offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11295
11296     while (i < forwardMostMove) {
11297         if (commentList[i] != NULL) {
11298             fprintf(f, "[%s]\n", commentList[i]);
11299         }
11300
11301         if ((i % 2) == 1) {
11302             fprintf(f, "%d. ...  %s\n", (i - offset)/2 + 1, parseList[i]);
11303             i++;
11304         } else {
11305             fprintf(f, "%d. %s  ", (i - offset)/2 + 1, parseList[i]);
11306             i++;
11307             if (commentList[i] != NULL) {
11308                 fprintf(f, "\n");
11309                 continue;
11310             }
11311             if (i >= forwardMostMove) {
11312                 fprintf(f, "\n");
11313                 break;
11314             }
11315             fprintf(f, "%s\n", parseList[i]);
11316             i++;
11317         }
11318     }
11319
11320     if (commentList[i] != NULL) {
11321         fprintf(f, "[%s]\n", commentList[i]);
11322     }
11323
11324     /* This isn't really the old style, but it's close enough */
11325     if (gameInfo.resultDetails != NULL &&
11326         gameInfo.resultDetails[0] != NULLCHAR) {
11327         fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11328                 gameInfo.resultDetails);
11329     } else {
11330         fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11331     }
11332
11333     fclose(f);
11334     return TRUE;
11335 }
11336
11337 /* Save the current game to open file f and close the file */
11338 int
11339 SaveGame(f, dummy, dummy2)
11340      FILE *f;
11341      int dummy;
11342      char *dummy2;
11343 {
11344     if (gameMode == EditPosition) EditPositionDone(TRUE);
11345     lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11346     if (appData.oldSaveStyle)
11347       return SaveGameOldStyle(f);
11348     else
11349       return SaveGamePGN(f);
11350 }
11351
11352 /* Save the current position to the given file */
11353 int
11354 SavePositionToFile(filename)
11355      char *filename;
11356 {
11357     FILE *f;
11358     char buf[MSG_SIZ];
11359
11360     if (strcmp(filename, "-") == 0) {
11361         return SavePosition(stdout, 0, NULL);
11362     } else {
11363         f = fopen(filename, "a");
11364         if (f == NULL) {
11365             snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11366             DisplayError(buf, errno);
11367             return FALSE;
11368         } else {
11369             safeStrCpy(buf, lastMsg, MSG_SIZ);
11370             DisplayMessage(_("Waiting for access to save file"), "");
11371             flock(fileno(f), LOCK_EX); // [HGM] lock
11372             DisplayMessage(_("Saving position"), "");
11373             lseek(fileno(f), 0, SEEK_END);     // better safe than sorry...
11374             SavePosition(f, 0, NULL);
11375             DisplayMessage(buf, "");
11376             return TRUE;
11377         }
11378     }
11379 }
11380
11381 /* Save the current position to the given open file and close the file */
11382 int
11383 SavePosition(f, dummy, dummy2)
11384      FILE *f;
11385      int dummy;
11386      char *dummy2;
11387 {
11388     time_t tm;
11389     char *fen;
11390
11391     if (gameMode == EditPosition) EditPositionDone(TRUE);
11392     if (appData.oldSaveStyle) {
11393         tm = time((time_t *) NULL);
11394
11395         fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11396         PrintOpponents(f);
11397         fprintf(f, "[--------------\n");
11398         PrintPosition(f, currentMove);
11399         fprintf(f, "--------------]\n");
11400     } else {
11401         fen = PositionToFEN(currentMove, NULL);
11402         fprintf(f, "%s\n", fen);
11403         free(fen);
11404     }
11405     fclose(f);
11406     return TRUE;
11407 }
11408
11409 void
11410 ReloadCmailMsgEvent(unregister)
11411      int unregister;
11412 {
11413 #if !WIN32
11414     static char *inFilename = NULL;
11415     static char *outFilename;
11416     int i;
11417     struct stat inbuf, outbuf;
11418     int status;
11419
11420     /* Any registered moves are unregistered if unregister is set, */
11421     /* i.e. invoked by the signal handler */
11422     if (unregister) {
11423         for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11424             cmailMoveRegistered[i] = FALSE;
11425             if (cmailCommentList[i] != NULL) {
11426                 free(cmailCommentList[i]);
11427                 cmailCommentList[i] = NULL;
11428             }
11429         }
11430         nCmailMovesRegistered = 0;
11431     }
11432
11433     for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11434         cmailResult[i] = CMAIL_NOT_RESULT;
11435     }
11436     nCmailResults = 0;
11437
11438     if (inFilename == NULL) {
11439         /* Because the filenames are static they only get malloced once  */
11440         /* and they never get freed                                      */
11441         inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11442         sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11443
11444         outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11445         sprintf(outFilename, "%s.out", appData.cmailGameName);
11446     }
11447
11448     status = stat(outFilename, &outbuf);
11449     if (status < 0) {
11450         cmailMailedMove = FALSE;
11451     } else {
11452         status = stat(inFilename, &inbuf);
11453         cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11454     }
11455
11456     /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11457        counts the games, notes how each one terminated, etc.
11458
11459        It would be nice to remove this kludge and instead gather all
11460        the information while building the game list.  (And to keep it
11461        in the game list nodes instead of having a bunch of fixed-size
11462        parallel arrays.)  Note this will require getting each game's
11463        termination from the PGN tags, as the game list builder does
11464        not process the game moves.  --mann
11465        */
11466     cmailMsgLoaded = TRUE;
11467     LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11468
11469     /* Load first game in the file or popup game menu */
11470     LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11471
11472 #endif /* !WIN32 */
11473     return;
11474 }
11475
11476 int
11477 RegisterMove()
11478 {
11479     FILE *f;
11480     char string[MSG_SIZ];
11481
11482     if (   cmailMailedMove
11483         || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11484         return TRUE;            /* Allow free viewing  */
11485     }
11486
11487     /* Unregister move to ensure that we don't leave RegisterMove        */
11488     /* with the move registered when the conditions for registering no   */
11489     /* longer hold                                                       */
11490     if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11491         cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11492         nCmailMovesRegistered --;
11493
11494         if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11495           {
11496               free(cmailCommentList[lastLoadGameNumber - 1]);
11497               cmailCommentList[lastLoadGameNumber - 1] = NULL;
11498           }
11499     }
11500
11501     if (cmailOldMove == -1) {
11502         DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11503         return FALSE;
11504     }
11505
11506     if (currentMove > cmailOldMove + 1) {
11507         DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11508         return FALSE;
11509     }
11510
11511     if (currentMove < cmailOldMove) {
11512         DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11513         return FALSE;
11514     }
11515
11516     if (forwardMostMove > currentMove) {
11517         /* Silently truncate extra moves */
11518         TruncateGame();
11519     }
11520
11521     if (   (currentMove == cmailOldMove + 1)
11522         || (   (currentMove == cmailOldMove)
11523             && (   (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11524                 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11525         if (gameInfo.result != GameUnfinished) {
11526             cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11527         }
11528
11529         if (commentList[currentMove] != NULL) {
11530             cmailCommentList[lastLoadGameNumber - 1]
11531               = StrSave(commentList[currentMove]);
11532         }
11533         safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11534
11535         if (appData.debugMode)
11536           fprintf(debugFP, "Saving %s for game %d\n",
11537                   cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11538
11539         snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11540
11541         f = fopen(string, "w");
11542         if (appData.oldSaveStyle) {
11543             SaveGameOldStyle(f); /* also closes the file */
11544
11545             snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11546             f = fopen(string, "w");
11547             SavePosition(f, 0, NULL); /* also closes the file */
11548         } else {
11549             fprintf(f, "{--------------\n");
11550             PrintPosition(f, currentMove);
11551             fprintf(f, "--------------}\n\n");
11552
11553             SaveGame(f, 0, NULL); /* also closes the file*/
11554         }
11555
11556         cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11557         nCmailMovesRegistered ++;
11558     } else if (nCmailGames == 1) {
11559         DisplayError(_("You have not made a move yet"), 0);
11560         return FALSE;
11561     }
11562
11563     return TRUE;
11564 }
11565
11566 void
11567 MailMoveEvent()
11568 {
11569 #if !WIN32
11570     static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11571     FILE *commandOutput;
11572     char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11573     int nBytes = 0;             /*  Suppress warnings on uninitialized variables    */
11574     int nBuffers;
11575     int i;
11576     int archived;
11577     char *arcDir;
11578
11579     if (! cmailMsgLoaded) {
11580         DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11581         return;
11582     }
11583
11584     if (nCmailGames == nCmailResults) {
11585         DisplayError(_("No unfinished games"), 0);
11586         return;
11587     }
11588
11589 #if CMAIL_PROHIBIT_REMAIL
11590     if (cmailMailedMove) {
11591       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);
11592         DisplayError(msg, 0);
11593         return;
11594     }
11595 #endif
11596
11597     if (! (cmailMailedMove || RegisterMove())) return;
11598
11599     if (   cmailMailedMove
11600         || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11601       snprintf(string, MSG_SIZ, partCommandString,
11602                appData.debugMode ? " -v" : "", appData.cmailGameName);
11603         commandOutput = popen(string, "r");
11604
11605         if (commandOutput == NULL) {
11606             DisplayError(_("Failed to invoke cmail"), 0);
11607         } else {
11608             for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
11609                 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
11610             }
11611             if (nBuffers > 1) {
11612                 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
11613                 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
11614                 nBytes = MSG_SIZ - 1;
11615             } else {
11616                 (void) memcpy(msg, buffer, nBytes);
11617             }
11618             *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
11619
11620             if(StrStr(msg, "Mailed cmail message to ") != NULL) {
11621                 cmailMailedMove = TRUE; /* Prevent >1 moves    */
11622
11623                 archived = TRUE;
11624                 for (i = 0; i < nCmailGames; i ++) {
11625                     if (cmailResult[i] == CMAIL_NOT_RESULT) {
11626                         archived = FALSE;
11627                     }
11628                 }
11629                 if (   archived
11630                     && (   (arcDir = (char *) getenv("CMAIL_ARCDIR"))
11631                         != NULL)) {
11632                   snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
11633                            arcDir,
11634                            appData.cmailGameName,
11635                            gameInfo.date);
11636                     LoadGameFromFile(buffer, 1, buffer, FALSE);
11637                     cmailMsgLoaded = FALSE;
11638                 }
11639             }
11640
11641             DisplayInformation(msg);
11642             pclose(commandOutput);
11643         }
11644     } else {
11645         if ((*cmailMsg) != '\0') {
11646             DisplayInformation(cmailMsg);
11647         }
11648     }
11649
11650     return;
11651 #endif /* !WIN32 */
11652 }
11653
11654 char *
11655 CmailMsg()
11656 {
11657 #if WIN32
11658     return NULL;
11659 #else
11660     int  prependComma = 0;
11661     char number[5];
11662     char string[MSG_SIZ];       /* Space for game-list */
11663     int  i;
11664
11665     if (!cmailMsgLoaded) return "";
11666
11667     if (cmailMailedMove) {
11668       snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
11669     } else {
11670         /* Create a list of games left */
11671       snprintf(string, MSG_SIZ, "[");
11672         for (i = 0; i < nCmailGames; i ++) {
11673             if (! (   cmailMoveRegistered[i]
11674                    || (cmailResult[i] == CMAIL_OLD_RESULT))) {
11675                 if (prependComma) {
11676                     snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
11677                 } else {
11678                     snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
11679                     prependComma = 1;
11680                 }
11681
11682                 strcat(string, number);
11683             }
11684         }
11685         strcat(string, "]");
11686
11687         if (nCmailMovesRegistered + nCmailResults == 0) {
11688             switch (nCmailGames) {
11689               case 1:
11690                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
11691                 break;
11692
11693               case 2:
11694                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
11695                 break;
11696
11697               default:
11698                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
11699                          nCmailGames);
11700                 break;
11701             }
11702         } else {
11703             switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
11704               case 1:
11705                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
11706                          string);
11707                 break;
11708
11709               case 0:
11710                 if (nCmailResults == nCmailGames) {
11711                   snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
11712                 } else {
11713                   snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
11714                 }
11715                 break;
11716
11717               default:
11718                 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
11719                          string);
11720             }
11721         }
11722     }
11723     return cmailMsg;
11724 #endif /* WIN32 */
11725 }
11726
11727 void
11728 ResetGameEvent()
11729 {
11730     if (gameMode == Training)
11731       SetTrainingModeOff();
11732
11733     Reset(TRUE, TRUE);
11734     cmailMsgLoaded = FALSE;
11735     if (appData.icsActive) {
11736       SendToICS(ics_prefix);
11737       SendToICS("refresh\n");
11738     }
11739 }
11740
11741 void
11742 ExitEvent(status)
11743      int status;
11744 {
11745     exiting++;
11746     if (exiting > 2) {
11747       /* Give up on clean exit */
11748       exit(status);
11749     }
11750     if (exiting > 1) {
11751       /* Keep trying for clean exit */
11752       return;
11753     }
11754
11755     if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
11756
11757     if (telnetISR != NULL) {
11758       RemoveInputSource(telnetISR);
11759     }
11760     if (icsPR != NoProc) {
11761       DestroyChildProcess(icsPR, TRUE);
11762     }
11763
11764     /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
11765     GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
11766
11767     /* [HGM] crash: the above GameEnds() is a dud if another one was running */
11768     /* make sure this other one finishes before killing it!                  */
11769     if(endingGame) { int count = 0;
11770         if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
11771         while(endingGame && count++ < 10) DoSleep(1);
11772         if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
11773     }
11774
11775     /* Kill off chess programs */
11776     if (first.pr != NoProc) {
11777         ExitAnalyzeMode();
11778
11779         DoSleep( appData.delayBeforeQuit );
11780         SendToProgram("quit\n", &first);
11781         DoSleep( appData.delayAfterQuit );
11782         DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
11783     }
11784     if (second.pr != NoProc) {
11785         DoSleep( appData.delayBeforeQuit );
11786         SendToProgram("quit\n", &second);
11787         DoSleep( appData.delayAfterQuit );
11788         DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
11789     }
11790     if (first.isr != NULL) {
11791         RemoveInputSource(first.isr);
11792     }
11793     if (second.isr != NULL) {
11794         RemoveInputSource(second.isr);
11795     }
11796
11797     ShutDownFrontEnd();
11798     exit(status);
11799 }
11800
11801 void
11802 PauseEvent()
11803 {
11804     if (appData.debugMode)
11805         fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
11806     if (pausing) {
11807         pausing = FALSE;
11808         ModeHighlight();
11809         if (gameMode == MachinePlaysWhite ||
11810             gameMode == MachinePlaysBlack) {
11811             StartClocks();
11812         } else {
11813             DisplayBothClocks();
11814         }
11815         if (gameMode == PlayFromGameFile) {
11816             if (appData.timeDelay >= 0)
11817                 AutoPlayGameLoop();
11818         } else if (gameMode == IcsExamining && pauseExamInvalid) {
11819             Reset(FALSE, TRUE);
11820             SendToICS(ics_prefix);
11821             SendToICS("refresh\n");
11822         } else if (currentMove < forwardMostMove) {
11823             ForwardInner(forwardMostMove);
11824         }
11825         pauseExamInvalid = FALSE;
11826     } else {
11827         switch (gameMode) {
11828           default:
11829             return;
11830           case IcsExamining:
11831             pauseExamForwardMostMove = forwardMostMove;
11832             pauseExamInvalid = FALSE;
11833             /* fall through */
11834           case IcsObserving:
11835           case IcsPlayingWhite:
11836           case IcsPlayingBlack:
11837             pausing = TRUE;
11838             ModeHighlight();
11839             return;
11840           case PlayFromGameFile:
11841             (void) StopLoadGameTimer();
11842             pausing = TRUE;
11843             ModeHighlight();
11844             break;
11845           case BeginningOfGame:
11846             if (appData.icsActive) return;
11847             /* else fall through */
11848           case MachinePlaysWhite:
11849           case MachinePlaysBlack:
11850           case TwoMachinesPlay:
11851             if (forwardMostMove == 0)
11852               return;           /* don't pause if no one has moved */
11853             if ((gameMode == MachinePlaysWhite &&
11854                  !WhiteOnMove(forwardMostMove)) ||
11855                 (gameMode == MachinePlaysBlack &&
11856                  WhiteOnMove(forwardMostMove))) {
11857                 StopClocks();
11858             }
11859             pausing = TRUE;
11860             ModeHighlight();
11861             break;
11862         }
11863     }
11864 }
11865
11866 void
11867 EditCommentEvent()
11868 {
11869     char title[MSG_SIZ];
11870
11871     if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
11872       safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
11873     } else {
11874       snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
11875                WhiteOnMove(currentMove - 1) ? " " : ".. ",
11876                parseList[currentMove - 1]);
11877     }
11878
11879     EditCommentPopUp(currentMove, title, commentList[currentMove]);
11880 }
11881
11882
11883 void
11884 EditTagsEvent()
11885 {
11886     char *tags = PGNTags(&gameInfo);
11887     EditTagsPopUp(tags, NULL);
11888     free(tags);
11889 }
11890
11891 void
11892 AnalyzeModeEvent()
11893 {
11894     if (appData.noChessProgram || gameMode == AnalyzeMode)
11895       return;
11896
11897     if (gameMode != AnalyzeFile) {
11898         if (!appData.icsEngineAnalyze) {
11899                EditGameEvent();
11900                if (gameMode != EditGame) return;
11901         }
11902         ResurrectChessProgram();
11903         SendToProgram("analyze\n", &first);
11904         first.analyzing = TRUE;
11905         /*first.maybeThinking = TRUE;*/
11906         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11907         EngineOutputPopUp();
11908     }
11909     if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
11910     pausing = FALSE;
11911     ModeHighlight();
11912     SetGameInfo();
11913
11914     StartAnalysisClock();
11915     GetTimeMark(&lastNodeCountTime);
11916     lastNodeCount = 0;
11917 }
11918
11919 void
11920 AnalyzeFileEvent()
11921 {
11922     if (appData.noChessProgram || gameMode == AnalyzeFile)
11923       return;
11924
11925     if (gameMode != AnalyzeMode) {
11926         EditGameEvent();
11927         if (gameMode != EditGame) return;
11928         ResurrectChessProgram();
11929         SendToProgram("analyze\n", &first);
11930         first.analyzing = TRUE;
11931         /*first.maybeThinking = TRUE;*/
11932         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
11933         EngineOutputPopUp();
11934     }
11935     gameMode = AnalyzeFile;
11936     pausing = FALSE;
11937     ModeHighlight();
11938     SetGameInfo();
11939
11940     StartAnalysisClock();
11941     GetTimeMark(&lastNodeCountTime);
11942     lastNodeCount = 0;
11943 }
11944
11945 void
11946 MachineWhiteEvent()
11947 {
11948     char buf[MSG_SIZ];
11949     char *bookHit = NULL;
11950
11951     if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
11952       return;
11953
11954
11955     if (gameMode == PlayFromGameFile ||
11956         gameMode == TwoMachinesPlay  ||
11957         gameMode == Training         ||
11958         gameMode == AnalyzeMode      ||
11959         gameMode == EndOfGame)
11960         EditGameEvent();
11961
11962     if (gameMode == EditPosition)
11963         EditPositionDone(TRUE);
11964
11965     if (!WhiteOnMove(currentMove)) {
11966         DisplayError(_("It is not White's turn"), 0);
11967         return;
11968     }
11969
11970     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
11971       ExitAnalyzeMode();
11972
11973     if (gameMode == EditGame || gameMode == AnalyzeMode ||
11974         gameMode == AnalyzeFile)
11975         TruncateGame();
11976
11977     ResurrectChessProgram();    /* in case it isn't running */
11978     if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
11979         gameMode = MachinePlaysWhite;
11980         ResetClocks();
11981     } else
11982     gameMode = MachinePlaysWhite;
11983     pausing = FALSE;
11984     ModeHighlight();
11985     SetGameInfo();
11986     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
11987     DisplayTitle(buf);
11988     if (first.sendName) {
11989       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
11990       SendToProgram(buf, &first);
11991     }
11992     if (first.sendTime) {
11993       if (first.useColors) {
11994         SendToProgram("black\n", &first); /*gnu kludge*/
11995       }
11996       SendTimeRemaining(&first, TRUE);
11997     }
11998     if (first.useColors) {
11999       SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12000     }
12001     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12002     SetMachineThinkingEnables();
12003     first.maybeThinking = TRUE;
12004     StartClocks();
12005     firstMove = FALSE;
12006
12007     if (appData.autoFlipView && !flipView) {
12008       flipView = !flipView;
12009       DrawPosition(FALSE, NULL);
12010       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12011     }
12012
12013     if(bookHit) { // [HGM] book: simulate book reply
12014         static char bookMove[MSG_SIZ]; // a bit generous?
12015
12016         programStats.nodes = programStats.depth = programStats.time =
12017         programStats.score = programStats.got_only_move = 0;
12018         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12019
12020         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12021         strcat(bookMove, bookHit);
12022         HandleMachineMove(bookMove, &first);
12023     }
12024 }
12025
12026 void
12027 MachineBlackEvent()
12028 {
12029   char buf[MSG_SIZ];
12030   char *bookHit = NULL;
12031
12032     if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12033         return;
12034
12035
12036     if (gameMode == PlayFromGameFile ||
12037         gameMode == TwoMachinesPlay  ||
12038         gameMode == Training         ||
12039         gameMode == AnalyzeMode      ||
12040         gameMode == EndOfGame)
12041         EditGameEvent();
12042
12043     if (gameMode == EditPosition)
12044         EditPositionDone(TRUE);
12045
12046     if (WhiteOnMove(currentMove)) {
12047         DisplayError(_("It is not Black's turn"), 0);
12048         return;
12049     }
12050
12051     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12052       ExitAnalyzeMode();
12053
12054     if (gameMode == EditGame || gameMode == AnalyzeMode ||
12055         gameMode == AnalyzeFile)
12056         TruncateGame();
12057
12058     ResurrectChessProgram();    /* in case it isn't running */
12059     gameMode = MachinePlaysBlack;
12060     pausing = FALSE;
12061     ModeHighlight();
12062     SetGameInfo();
12063     snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12064     DisplayTitle(buf);
12065     if (first.sendName) {
12066       snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12067       SendToProgram(buf, &first);
12068     }
12069     if (first.sendTime) {
12070       if (first.useColors) {
12071         SendToProgram("white\n", &first); /*gnu kludge*/
12072       }
12073       SendTimeRemaining(&first, FALSE);
12074     }
12075     if (first.useColors) {
12076       SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12077     }
12078     bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12079     SetMachineThinkingEnables();
12080     first.maybeThinking = TRUE;
12081     StartClocks();
12082
12083     if (appData.autoFlipView && flipView) {
12084       flipView = !flipView;
12085       DrawPosition(FALSE, NULL);
12086       DisplayBothClocks();       // [HGM] logo: clocks might have to be exchanged;
12087     }
12088     if(bookHit) { // [HGM] book: simulate book reply
12089         static char bookMove[MSG_SIZ]; // a bit generous?
12090
12091         programStats.nodes = programStats.depth = programStats.time =
12092         programStats.score = programStats.got_only_move = 0;
12093         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12094
12095         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12096         strcat(bookMove, bookHit);
12097         HandleMachineMove(bookMove, &first);
12098     }
12099 }
12100
12101
12102 void
12103 DisplayTwoMachinesTitle()
12104 {
12105     char buf[MSG_SIZ];
12106     if (appData.matchGames > 0) {
12107         if (first.twoMachinesColor[0] == 'w') {
12108           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12109                    gameInfo.white, gameInfo.black,
12110                    first.matchWins, second.matchWins,
12111                    matchGame - 1 - (first.matchWins + second.matchWins));
12112         } else {
12113           snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12114                    gameInfo.white, gameInfo.black,
12115                    second.matchWins, first.matchWins,
12116                    matchGame - 1 - (first.matchWins + second.matchWins));
12117         }
12118     } else {
12119       snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12120     }
12121     DisplayTitle(buf);
12122 }
12123
12124 void
12125 SettingsMenuIfReady()
12126 {
12127   if (second.lastPing != second.lastPong) {
12128     DisplayMessage("", _("Waiting for second chess program"));
12129     ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12130     return;
12131   }
12132   ThawUI();
12133   DisplayMessage("", "");
12134   SettingsPopUp(&second);
12135 }
12136
12137 int
12138 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12139 {
12140     char buf[MSG_SIZ];
12141     if (cps->pr == NULL) {
12142         StartChessProgram(cps);
12143         if (cps->protocolVersion == 1) {
12144           retry();
12145         } else {
12146           /* kludge: allow timeout for initial "feature" command */
12147           FreezeUI();
12148           snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12149           DisplayMessage("", buf);
12150           ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12151         }
12152         return 1;
12153     }
12154     return 0;
12155 }
12156
12157 void
12158 TwoMachinesEvent P((void))
12159 {
12160     int i;
12161     char buf[MSG_SIZ];
12162     ChessProgramState *onmove;
12163     char *bookHit = NULL;
12164     static int stalling = 0;
12165
12166     if (appData.noChessProgram) return;
12167
12168     switch (gameMode) {
12169       case TwoMachinesPlay:
12170         return;
12171       case MachinePlaysWhite:
12172       case MachinePlaysBlack:
12173         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12174             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12175             return;
12176         }
12177         /* fall through */
12178       case BeginningOfGame:
12179       case PlayFromGameFile:
12180       case EndOfGame:
12181         EditGameEvent();
12182         if (gameMode != EditGame) return;
12183         break;
12184       case EditPosition:
12185         EditPositionDone(TRUE);
12186         break;
12187       case AnalyzeMode:
12188       case AnalyzeFile:
12189         ExitAnalyzeMode();
12190         break;
12191       case EditGame:
12192       default:
12193         break;
12194     }
12195
12196 //    forwardMostMove = currentMove;
12197     TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12198     ResurrectChessProgram();    /* in case first program isn't running */
12199
12200     if(WaitForEngine(&second, TwoMachinesEventIfReady)) return;
12201     if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12202       DisplayMessage("", _("Waiting for first chess program"));
12203       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12204       return;
12205     }
12206     if(!stalling) {
12207       InitChessProgram(&second, FALSE);
12208       SendToProgram("force\n", &second);
12209     }
12210     if(second.lastPing != second.lastPong) { // [HGM] second engine might have to reallocate hash
12211       if(!stalling) DisplayMessage("", _("Waiting for second chess program"));
12212       stalling = 1;
12213       ScheduleDelayedEvent(TwoMachinesEvent, 10);
12214       return;
12215     }
12216     stalling = 0;
12217     DisplayMessage("", "");
12218     if (startedFromSetupPosition) {
12219         SendBoard(&second, backwardMostMove);
12220     if (appData.debugMode) {
12221         fprintf(debugFP, "Two Machines\n");
12222     }
12223     }
12224     for (i = backwardMostMove; i < forwardMostMove; i++) {
12225         SendMoveToProgram(i, &second);
12226     }
12227
12228     gameMode = TwoMachinesPlay;
12229     pausing = FALSE;
12230     ModeHighlight();
12231     SetGameInfo();
12232     DisplayTwoMachinesTitle();
12233     firstMove = TRUE;
12234     if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12235         onmove = &first;
12236     } else {
12237         onmove = &second;
12238     }
12239
12240     SendToProgram(first.computerString, &first);
12241     if (first.sendName) {
12242       snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12243       SendToProgram(buf, &first);
12244     }
12245     SendToProgram(second.computerString, &second);
12246     if (second.sendName) {
12247       snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12248       SendToProgram(buf, &second);
12249     }
12250
12251     ResetClocks();
12252     if (!first.sendTime || !second.sendTime) {
12253         timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12254         timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12255     }
12256     if (onmove->sendTime) {
12257       if (onmove->useColors) {
12258         SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12259       }
12260       SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12261     }
12262     if (onmove->useColors) {
12263       SendToProgram(onmove->twoMachinesColor, onmove);
12264     }
12265     bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12266 //    SendToProgram("go\n", onmove);
12267     onmove->maybeThinking = TRUE;
12268     SetMachineThinkingEnables();
12269
12270     StartClocks();
12271
12272     if(bookHit) { // [HGM] book: simulate book reply
12273         static char bookMove[MSG_SIZ]; // a bit generous?
12274
12275         programStats.nodes = programStats.depth = programStats.time =
12276         programStats.score = programStats.got_only_move = 0;
12277         sprintf(programStats.movelist, "%s (xbook)", bookHit);
12278
12279         safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12280         strcat(bookMove, bookHit);
12281         savedMessage = bookMove; // args for deferred call
12282         savedState = onmove;
12283         ScheduleDelayedEvent(DeferredBookMove, 1);
12284     }
12285 }
12286
12287 void
12288 TrainingEvent()
12289 {
12290     if (gameMode == Training) {
12291       SetTrainingModeOff();
12292       gameMode = PlayFromGameFile;
12293       DisplayMessage("", _("Training mode off"));
12294     } else {
12295       gameMode = Training;
12296       animateTraining = appData.animate;
12297
12298       /* make sure we are not already at the end of the game */
12299       if (currentMove < forwardMostMove) {
12300         SetTrainingModeOn();
12301         DisplayMessage("", _("Training mode on"));
12302       } else {
12303         gameMode = PlayFromGameFile;
12304         DisplayError(_("Already at end of game"), 0);
12305       }
12306     }
12307     ModeHighlight();
12308 }
12309
12310 void
12311 IcsClientEvent()
12312 {
12313     if (!appData.icsActive) return;
12314     switch (gameMode) {
12315       case IcsPlayingWhite:
12316       case IcsPlayingBlack:
12317       case IcsObserving:
12318       case IcsIdle:
12319       case BeginningOfGame:
12320       case IcsExamining:
12321         return;
12322
12323       case EditGame:
12324         break;
12325
12326       case EditPosition:
12327         EditPositionDone(TRUE);
12328         break;
12329
12330       case AnalyzeMode:
12331       case AnalyzeFile:
12332         ExitAnalyzeMode();
12333         break;
12334
12335       default:
12336         EditGameEvent();
12337         break;
12338     }
12339
12340     gameMode = IcsIdle;
12341     ModeHighlight();
12342     return;
12343 }
12344
12345
12346 void
12347 EditGameEvent()
12348 {
12349     int i;
12350
12351     switch (gameMode) {
12352       case Training:
12353         SetTrainingModeOff();
12354         break;
12355       case MachinePlaysWhite:
12356       case MachinePlaysBlack:
12357       case BeginningOfGame:
12358         SendToProgram("force\n", &first);
12359         SetUserThinkingEnables();
12360         break;
12361       case PlayFromGameFile:
12362         (void) StopLoadGameTimer();
12363         if (gameFileFP != NULL) {
12364             gameFileFP = NULL;
12365         }
12366         break;
12367       case EditPosition:
12368         EditPositionDone(TRUE);
12369         break;
12370       case AnalyzeMode:
12371       case AnalyzeFile:
12372         ExitAnalyzeMode();
12373         SendToProgram("force\n", &first);
12374         break;
12375       case TwoMachinesPlay:
12376         GameEnds(EndOfFile, NULL, GE_PLAYER);
12377         ResurrectChessProgram();
12378         SetUserThinkingEnables();
12379         break;
12380       case EndOfGame:
12381         ResurrectChessProgram();
12382         break;
12383       case IcsPlayingBlack:
12384       case IcsPlayingWhite:
12385         DisplayError(_("Warning: You are still playing a game"), 0);
12386         break;
12387       case IcsObserving:
12388         DisplayError(_("Warning: You are still observing a game"), 0);
12389         break;
12390       case IcsExamining:
12391         DisplayError(_("Warning: You are still examining a game"), 0);
12392         break;
12393       case IcsIdle:
12394         break;
12395       case EditGame:
12396       default:
12397         return;
12398     }
12399
12400     pausing = FALSE;
12401     StopClocks();
12402     first.offeredDraw = second.offeredDraw = 0;
12403
12404     if (gameMode == PlayFromGameFile) {
12405         whiteTimeRemaining = timeRemaining[0][currentMove];
12406         blackTimeRemaining = timeRemaining[1][currentMove];
12407         DisplayTitle("");
12408     }
12409
12410     if (gameMode == MachinePlaysWhite ||
12411         gameMode == MachinePlaysBlack ||
12412         gameMode == TwoMachinesPlay ||
12413         gameMode == EndOfGame) {
12414         i = forwardMostMove;
12415         while (i > currentMove) {
12416             SendToProgram("undo\n", &first);
12417             i--;
12418         }
12419         whiteTimeRemaining = timeRemaining[0][currentMove];
12420         blackTimeRemaining = timeRemaining[1][currentMove];
12421         DisplayBothClocks();
12422         if (whiteFlag || blackFlag) {
12423             whiteFlag = blackFlag = 0;
12424         }
12425         DisplayTitle("");
12426     }
12427
12428     gameMode = EditGame;
12429     ModeHighlight();
12430     SetGameInfo();
12431 }
12432
12433
12434 void
12435 EditPositionEvent()
12436 {
12437     if (gameMode == EditPosition) {
12438         EditGameEvent();
12439         return;
12440     }
12441
12442     EditGameEvent();
12443     if (gameMode != EditGame) return;
12444
12445     gameMode = EditPosition;
12446     ModeHighlight();
12447     SetGameInfo();
12448     if (currentMove > 0)
12449       CopyBoard(boards[0], boards[currentMove]);
12450
12451     blackPlaysFirst = !WhiteOnMove(currentMove);
12452     ResetClocks();
12453     currentMove = forwardMostMove = backwardMostMove = 0;
12454     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12455     DisplayMove(-1);
12456 }
12457
12458 void
12459 ExitAnalyzeMode()
12460 {
12461     /* [DM] icsEngineAnalyze - possible call from other functions */
12462     if (appData.icsEngineAnalyze) {
12463         appData.icsEngineAnalyze = FALSE;
12464
12465         DisplayMessage("",_("Close ICS engine analyze..."));
12466     }
12467     if (first.analysisSupport && first.analyzing) {
12468       SendToProgram("exit\n", &first);
12469       first.analyzing = FALSE;
12470     }
12471     thinkOutput[0] = NULLCHAR;
12472 }
12473
12474 void
12475 EditPositionDone(Boolean fakeRights)
12476 {
12477     int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12478
12479     startedFromSetupPosition = TRUE;
12480     InitChessProgram(&first, FALSE);
12481     if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12482       boards[0][EP_STATUS] = EP_NONE;
12483       boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12484     if(boards[0][0][BOARD_WIDTH>>1] == king) {
12485         boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12486         boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12487       } else boards[0][CASTLING][2] = NoRights;
12488     if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12489         boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12490         boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12491       } else boards[0][CASTLING][5] = NoRights;
12492     }
12493     SendToProgram("force\n", &first);
12494     if (blackPlaysFirst) {
12495         safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12496         safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12497         currentMove = forwardMostMove = backwardMostMove = 1;
12498         CopyBoard(boards[1], boards[0]);
12499     } else {
12500         currentMove = forwardMostMove = backwardMostMove = 0;
12501     }
12502     SendBoard(&first, forwardMostMove);
12503     if (appData.debugMode) {
12504         fprintf(debugFP, "EditPosDone\n");
12505     }
12506     DisplayTitle("");
12507     timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12508     timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12509     gameMode = EditGame;
12510     ModeHighlight();
12511     HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12512     ClearHighlights(); /* [AS] */
12513 }
12514
12515 /* Pause for `ms' milliseconds */
12516 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12517 void
12518 TimeDelay(ms)
12519      long ms;
12520 {
12521     TimeMark m1, m2;
12522
12523     GetTimeMark(&m1);
12524     do {
12525         GetTimeMark(&m2);
12526     } while (SubtractTimeMarks(&m2, &m1) < ms);
12527 }
12528
12529 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12530 void
12531 SendMultiLineToICS(buf)
12532      char *buf;
12533 {
12534     char temp[MSG_SIZ+1], *p;
12535     int len;
12536
12537     len = strlen(buf);
12538     if (len > MSG_SIZ)
12539       len = MSG_SIZ;
12540
12541     strncpy(temp, buf, len);
12542     temp[len] = 0;
12543
12544     p = temp;
12545     while (*p) {
12546         if (*p == '\n' || *p == '\r')
12547           *p = ' ';
12548         ++p;
12549     }
12550
12551     strcat(temp, "\n");
12552     SendToICS(temp);
12553     SendToPlayer(temp, strlen(temp));
12554 }
12555
12556 void
12557 SetWhiteToPlayEvent()
12558 {
12559     if (gameMode == EditPosition) {
12560         blackPlaysFirst = FALSE;
12561         DisplayBothClocks();    /* works because currentMove is 0 */
12562     } else if (gameMode == IcsExamining) {
12563         SendToICS(ics_prefix);
12564         SendToICS("tomove white\n");
12565     }
12566 }
12567
12568 void
12569 SetBlackToPlayEvent()
12570 {
12571     if (gameMode == EditPosition) {
12572         blackPlaysFirst = TRUE;
12573         currentMove = 1;        /* kludge */
12574         DisplayBothClocks();
12575         currentMove = 0;
12576     } else if (gameMode == IcsExamining) {
12577         SendToICS(ics_prefix);
12578         SendToICS("tomove black\n");
12579     }
12580 }
12581
12582 void
12583 EditPositionMenuEvent(selection, x, y)
12584      ChessSquare selection;
12585      int x, y;
12586 {
12587     char buf[MSG_SIZ];
12588     ChessSquare piece = boards[0][y][x];
12589
12590     if (gameMode != EditPosition && gameMode != IcsExamining) return;
12591
12592     switch (selection) {
12593       case ClearBoard:
12594         if (gameMode == IcsExamining && ics_type == ICS_FICS) {
12595             SendToICS(ics_prefix);
12596             SendToICS("bsetup clear\n");
12597         } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
12598             SendToICS(ics_prefix);
12599             SendToICS("clearboard\n");
12600         } else {
12601             for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
12602                 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
12603                 for (y = 0; y < BOARD_HEIGHT; y++) {
12604                     if (gameMode == IcsExamining) {
12605                         if (boards[currentMove][y][x] != EmptySquare) {
12606                           snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
12607                                     AAA + x, ONE + y);
12608                             SendToICS(buf);
12609                         }
12610                     } else {
12611                         boards[0][y][x] = p;
12612                     }
12613                 }
12614             }
12615         }
12616         if (gameMode == EditPosition) {
12617             DrawPosition(FALSE, boards[0]);
12618         }
12619         break;
12620
12621       case WhitePlay:
12622         SetWhiteToPlayEvent();
12623         break;
12624
12625       case BlackPlay:
12626         SetBlackToPlayEvent();
12627         break;
12628
12629       case EmptySquare:
12630         if (gameMode == IcsExamining) {
12631             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12632             snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
12633             SendToICS(buf);
12634         } else {
12635             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12636                 if(x == BOARD_LEFT-2) {
12637                     if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
12638                     boards[0][y][1] = 0;
12639                 } else
12640                 if(x == BOARD_RGHT+1) {
12641                     if(y >= gameInfo.holdingsSize) break;
12642                     boards[0][y][BOARD_WIDTH-2] = 0;
12643                 } else break;
12644             }
12645             boards[0][y][x] = EmptySquare;
12646             DrawPosition(FALSE, boards[0]);
12647         }
12648         break;
12649
12650       case PromotePiece:
12651         if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
12652            piece >= (int)BlackPawn && piece < (int)BlackMan   ) {
12653             selection = (ChessSquare) (PROMOTED piece);
12654         } else if(piece == EmptySquare) selection = WhiteSilver;
12655         else selection = (ChessSquare)((int)piece - 1);
12656         goto defaultlabel;
12657
12658       case DemotePiece:
12659         if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
12660            piece > (int)BlackMan && piece <= (int)BlackKing   ) {
12661             selection = (ChessSquare) (DEMOTED piece);
12662         } else if(piece == EmptySquare) selection = BlackSilver;
12663         else selection = (ChessSquare)((int)piece + 1);
12664         goto defaultlabel;
12665
12666       case WhiteQueen:
12667       case BlackQueen:
12668         if(gameInfo.variant == VariantShatranj ||
12669            gameInfo.variant == VariantXiangqi  ||
12670            gameInfo.variant == VariantCourier  ||
12671            gameInfo.variant == VariantMakruk     )
12672             selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
12673         goto defaultlabel;
12674
12675       case WhiteKing:
12676       case BlackKing:
12677         if(gameInfo.variant == VariantXiangqi)
12678             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
12679         if(gameInfo.variant == VariantKnightmate)
12680             selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
12681       default:
12682         defaultlabel:
12683         if (gameMode == IcsExamining) {
12684             if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
12685             snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
12686                      PieceToChar(selection), AAA + x, ONE + y);
12687             SendToICS(buf);
12688         } else {
12689             if(x < BOARD_LEFT || x >= BOARD_RGHT) {
12690                 int n;
12691                 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
12692                     n = PieceToNumber(selection - BlackPawn);
12693                     if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
12694                     boards[0][BOARD_HEIGHT-1-n][0] = selection;
12695                     boards[0][BOARD_HEIGHT-1-n][1]++;
12696                 } else
12697                 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
12698                     n = PieceToNumber(selection);
12699                     if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
12700                     boards[0][n][BOARD_WIDTH-1] = selection;
12701                     boards[0][n][BOARD_WIDTH-2]++;
12702                 }
12703             } else
12704             boards[0][y][x] = selection;
12705             DrawPosition(TRUE, boards[0]);
12706         }
12707         break;
12708     }
12709 }
12710
12711
12712 void
12713 DropMenuEvent(selection, x, y)
12714      ChessSquare selection;
12715      int x, y;
12716 {
12717     ChessMove moveType;
12718
12719     switch (gameMode) {
12720       case IcsPlayingWhite:
12721       case MachinePlaysBlack:
12722         if (!WhiteOnMove(currentMove)) {
12723             DisplayMoveError(_("It is Black's turn"));
12724             return;
12725         }
12726         moveType = WhiteDrop;
12727         break;
12728       case IcsPlayingBlack:
12729       case MachinePlaysWhite:
12730         if (WhiteOnMove(currentMove)) {
12731             DisplayMoveError(_("It is White's turn"));
12732             return;
12733         }
12734         moveType = BlackDrop;
12735         break;
12736       case EditGame:
12737         moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
12738         break;
12739       default:
12740         return;
12741     }
12742
12743     if (moveType == BlackDrop && selection < BlackPawn) {
12744       selection = (ChessSquare) ((int) selection
12745                                  + (int) BlackPawn - (int) WhitePawn);
12746     }
12747     if (boards[currentMove][y][x] != EmptySquare) {
12748         DisplayMoveError(_("That square is occupied"));
12749         return;
12750     }
12751
12752     FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
12753 }
12754
12755 void
12756 AcceptEvent()
12757 {
12758     /* Accept a pending offer of any kind from opponent */
12759
12760     if (appData.icsActive) {
12761         SendToICS(ics_prefix);
12762         SendToICS("accept\n");
12763     } else if (cmailMsgLoaded) {
12764         if (currentMove == cmailOldMove &&
12765             commentList[cmailOldMove] != NULL &&
12766             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12767                    "Black offers a draw" : "White offers a draw")) {
12768             TruncateGame();
12769             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12770             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12771         } else {
12772             DisplayError(_("There is no pending offer on this move"), 0);
12773             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12774         }
12775     } else {
12776         /* Not used for offers from chess program */
12777     }
12778 }
12779
12780 void
12781 DeclineEvent()
12782 {
12783     /* Decline a pending offer of any kind from opponent */
12784
12785     if (appData.icsActive) {
12786         SendToICS(ics_prefix);
12787         SendToICS("decline\n");
12788     } else if (cmailMsgLoaded) {
12789         if (currentMove == cmailOldMove &&
12790             commentList[cmailOldMove] != NULL &&
12791             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12792                    "Black offers a draw" : "White offers a draw")) {
12793 #ifdef NOTDEF
12794             AppendComment(cmailOldMove, "Draw declined", TRUE);
12795             DisplayComment(cmailOldMove - 1, "Draw declined");
12796 #endif /*NOTDEF*/
12797         } else {
12798             DisplayError(_("There is no pending offer on this move"), 0);
12799         }
12800     } else {
12801         /* Not used for offers from chess program */
12802     }
12803 }
12804
12805 void
12806 RematchEvent()
12807 {
12808     /* Issue ICS rematch command */
12809     if (appData.icsActive) {
12810         SendToICS(ics_prefix);
12811         SendToICS("rematch\n");
12812     }
12813 }
12814
12815 void
12816 CallFlagEvent()
12817 {
12818     /* Call your opponent's flag (claim a win on time) */
12819     if (appData.icsActive) {
12820         SendToICS(ics_prefix);
12821         SendToICS("flag\n");
12822     } else {
12823         switch (gameMode) {
12824           default:
12825             return;
12826           case MachinePlaysWhite:
12827             if (whiteFlag) {
12828                 if (blackFlag)
12829                   GameEnds(GameIsDrawn, "Both players ran out of time",
12830                            GE_PLAYER);
12831                 else
12832                   GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
12833             } else {
12834                 DisplayError(_("Your opponent is not out of time"), 0);
12835             }
12836             break;
12837           case MachinePlaysBlack:
12838             if (blackFlag) {
12839                 if (whiteFlag)
12840                   GameEnds(GameIsDrawn, "Both players ran out of time",
12841                            GE_PLAYER);
12842                 else
12843                   GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
12844             } else {
12845                 DisplayError(_("Your opponent is not out of time"), 0);
12846             }
12847             break;
12848         }
12849     }
12850 }
12851
12852 void
12853 ClockClick(int which)
12854 {       // [HGM] code moved to back-end from winboard.c
12855         if(which) { // black clock
12856           if (gameMode == EditPosition || gameMode == IcsExamining) {
12857             if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12858             SetBlackToPlayEvent();
12859           } else if (gameMode == EditGame || shiftKey) {
12860             AdjustClock(which, -1);
12861           } else if (gameMode == IcsPlayingWhite ||
12862                      gameMode == MachinePlaysBlack) {
12863             CallFlagEvent();
12864           }
12865         } else { // white clock
12866           if (gameMode == EditPosition || gameMode == IcsExamining) {
12867             if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
12868             SetWhiteToPlayEvent();
12869           } else if (gameMode == EditGame || shiftKey) {
12870             AdjustClock(which, -1);
12871           } else if (gameMode == IcsPlayingBlack ||
12872                    gameMode == MachinePlaysWhite) {
12873             CallFlagEvent();
12874           }
12875         }
12876 }
12877
12878 void
12879 DrawEvent()
12880 {
12881     /* Offer draw or accept pending draw offer from opponent */
12882
12883     if (appData.icsActive) {
12884         /* Note: tournament rules require draw offers to be
12885            made after you make your move but before you punch
12886            your clock.  Currently ICS doesn't let you do that;
12887            instead, you immediately punch your clock after making
12888            a move, but you can offer a draw at any time. */
12889
12890         SendToICS(ics_prefix);
12891         SendToICS("draw\n");
12892         userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
12893     } else if (cmailMsgLoaded) {
12894         if (currentMove == cmailOldMove &&
12895             commentList[cmailOldMove] != NULL &&
12896             StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
12897                    "Black offers a draw" : "White offers a draw")) {
12898             GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
12899             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
12900         } else if (currentMove == cmailOldMove + 1) {
12901             char *offer = WhiteOnMove(cmailOldMove) ?
12902               "White offers a draw" : "Black offers a draw";
12903             AppendComment(currentMove, offer, TRUE);
12904             DisplayComment(currentMove - 1, offer);
12905             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
12906         } else {
12907             DisplayError(_("You must make your move before offering a draw"), 0);
12908             cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
12909         }
12910     } else if (first.offeredDraw) {
12911         GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
12912     } else {
12913         if (first.sendDrawOffers) {
12914             SendToProgram("draw\n", &first);
12915             userOfferedDraw = TRUE;
12916         }
12917     }
12918 }
12919
12920 void
12921 AdjournEvent()
12922 {
12923     /* Offer Adjourn or accept pending Adjourn offer from opponent */
12924
12925     if (appData.icsActive) {
12926         SendToICS(ics_prefix);
12927         SendToICS("adjourn\n");
12928     } else {
12929         /* Currently GNU Chess doesn't offer or accept Adjourns */
12930     }
12931 }
12932
12933
12934 void
12935 AbortEvent()
12936 {
12937     /* Offer Abort or accept pending Abort offer from opponent */
12938
12939     if (appData.icsActive) {
12940         SendToICS(ics_prefix);
12941         SendToICS("abort\n");
12942     } else {
12943         GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
12944     }
12945 }
12946
12947 void
12948 ResignEvent()
12949 {
12950     /* Resign.  You can do this even if it's not your turn. */
12951
12952     if (appData.icsActive) {
12953         SendToICS(ics_prefix);
12954         SendToICS("resign\n");
12955     } else {
12956         switch (gameMode) {
12957           case MachinePlaysWhite:
12958             GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12959             break;
12960           case MachinePlaysBlack:
12961             GameEnds(BlackWins, "White resigns", GE_PLAYER);
12962             break;
12963           case EditGame:
12964             if (cmailMsgLoaded) {
12965                 TruncateGame();
12966                 if (WhiteOnMove(cmailOldMove)) {
12967                     GameEnds(BlackWins, "White resigns", GE_PLAYER);
12968                 } else {
12969                     GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
12970                 }
12971                 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
12972             }
12973             break;
12974           default:
12975             break;
12976         }
12977     }
12978 }
12979
12980
12981 void
12982 StopObservingEvent()
12983 {
12984     /* Stop observing current games */
12985     SendToICS(ics_prefix);
12986     SendToICS("unobserve\n");
12987 }
12988
12989 void
12990 StopExaminingEvent()
12991 {
12992     /* Stop observing current game */
12993     SendToICS(ics_prefix);
12994     SendToICS("unexamine\n");
12995 }
12996
12997 void
12998 ForwardInner(target)
12999      int target;
13000 {
13001     int limit;
13002
13003     if (appData.debugMode)
13004         fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13005                 target, currentMove, forwardMostMove);
13006
13007     if (gameMode == EditPosition)
13008       return;
13009
13010     if (gameMode == PlayFromGameFile && !pausing)
13011       PauseEvent();
13012
13013     if (gameMode == IcsExamining && pausing)
13014       limit = pauseExamForwardMostMove;
13015     else
13016       limit = forwardMostMove;
13017
13018     if (target > limit) target = limit;
13019
13020     if (target > 0 && moveList[target - 1][0]) {
13021         int fromX, fromY, toX, toY;
13022         toX = moveList[target - 1][2] - AAA;
13023         toY = moveList[target - 1][3] - ONE;
13024         if (moveList[target - 1][1] == '@') {
13025             if (appData.highlightLastMove) {
13026                 SetHighlights(-1, -1, toX, toY);
13027             }
13028         } else {
13029             fromX = moveList[target - 1][0] - AAA;
13030             fromY = moveList[target - 1][1] - ONE;
13031             if (target == currentMove + 1) {
13032                 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13033             }
13034             if (appData.highlightLastMove) {
13035                 SetHighlights(fromX, fromY, toX, toY);
13036             }
13037         }
13038     }
13039     if (gameMode == EditGame || gameMode == AnalyzeMode ||
13040         gameMode == Training || gameMode == PlayFromGameFile ||
13041         gameMode == AnalyzeFile) {
13042         while (currentMove < target) {
13043             SendMoveToProgram(currentMove++, &first);
13044         }
13045     } else {
13046         currentMove = target;
13047     }
13048
13049     if (gameMode == EditGame || gameMode == EndOfGame) {
13050         whiteTimeRemaining = timeRemaining[0][currentMove];
13051         blackTimeRemaining = timeRemaining[1][currentMove];
13052     }
13053     DisplayBothClocks();
13054     DisplayMove(currentMove - 1);
13055     DrawPosition(FALSE, boards[currentMove]);
13056     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13057     if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13058         DisplayComment(currentMove - 1, commentList[currentMove]);
13059     }
13060 }
13061
13062
13063 void
13064 ForwardEvent()
13065 {
13066     if (gameMode == IcsExamining && !pausing) {
13067         SendToICS(ics_prefix);
13068         SendToICS("forward\n");
13069     } else {
13070         ForwardInner(currentMove + 1);
13071     }
13072 }
13073
13074 void
13075 ToEndEvent()
13076 {
13077     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13078         /* to optimze, we temporarily turn off analysis mode while we feed
13079          * the remaining moves to the engine. Otherwise we get analysis output
13080          * after each move.
13081          */
13082         if (first.analysisSupport) {
13083           SendToProgram("exit\nforce\n", &first);
13084           first.analyzing = FALSE;
13085         }
13086     }
13087
13088     if (gameMode == IcsExamining && !pausing) {
13089         SendToICS(ics_prefix);
13090         SendToICS("forward 999999\n");
13091     } else {
13092         ForwardInner(forwardMostMove);
13093     }
13094
13095     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13096         /* we have fed all the moves, so reactivate analysis mode */
13097         SendToProgram("analyze\n", &first);
13098         first.analyzing = TRUE;
13099         /*first.maybeThinking = TRUE;*/
13100         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13101     }
13102 }
13103
13104 void
13105 BackwardInner(target)
13106      int target;
13107 {
13108     int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13109
13110     if (appData.debugMode)
13111         fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13112                 target, currentMove, forwardMostMove);
13113
13114     if (gameMode == EditPosition) return;
13115     if (currentMove <= backwardMostMove) {
13116         ClearHighlights();
13117         DrawPosition(full_redraw, boards[currentMove]);
13118         return;
13119     }
13120     if (gameMode == PlayFromGameFile && !pausing)
13121       PauseEvent();
13122
13123     if (moveList[target][0]) {
13124         int fromX, fromY, toX, toY;
13125         toX = moveList[target][2] - AAA;
13126         toY = moveList[target][3] - ONE;
13127         if (moveList[target][1] == '@') {
13128             if (appData.highlightLastMove) {
13129                 SetHighlights(-1, -1, toX, toY);
13130             }
13131         } else {
13132             fromX = moveList[target][0] - AAA;
13133             fromY = moveList[target][1] - ONE;
13134             if (target == currentMove - 1) {
13135                 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13136             }
13137             if (appData.highlightLastMove) {
13138                 SetHighlights(fromX, fromY, toX, toY);
13139             }
13140         }
13141     }
13142     if (gameMode == EditGame || gameMode==AnalyzeMode ||
13143         gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13144         while (currentMove > target) {
13145             SendToProgram("undo\n", &first);
13146             currentMove--;
13147         }
13148     } else {
13149         currentMove = target;
13150     }
13151
13152     if (gameMode == EditGame || gameMode == EndOfGame) {
13153         whiteTimeRemaining = timeRemaining[0][currentMove];
13154         blackTimeRemaining = timeRemaining[1][currentMove];
13155     }
13156     DisplayBothClocks();
13157     DisplayMove(currentMove - 1);
13158     DrawPosition(full_redraw, boards[currentMove]);
13159     HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13160     // [HGM] PV info: routine tests if comment empty
13161     DisplayComment(currentMove - 1, commentList[currentMove]);
13162 }
13163
13164 void
13165 BackwardEvent()
13166 {
13167     if (gameMode == IcsExamining && !pausing) {
13168         SendToICS(ics_prefix);
13169         SendToICS("backward\n");
13170     } else {
13171         BackwardInner(currentMove - 1);
13172     }
13173 }
13174
13175 void
13176 ToStartEvent()
13177 {
13178     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13179         /* to optimize, we temporarily turn off analysis mode while we undo
13180          * all the moves. Otherwise we get analysis output after each undo.
13181          */
13182         if (first.analysisSupport) {
13183           SendToProgram("exit\nforce\n", &first);
13184           first.analyzing = FALSE;
13185         }
13186     }
13187
13188     if (gameMode == IcsExamining && !pausing) {
13189         SendToICS(ics_prefix);
13190         SendToICS("backward 999999\n");
13191     } else {
13192         BackwardInner(backwardMostMove);
13193     }
13194
13195     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13196         /* we have fed all the moves, so reactivate analysis mode */
13197         SendToProgram("analyze\n", &first);
13198         first.analyzing = TRUE;
13199         /*first.maybeThinking = TRUE;*/
13200         first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13201     }
13202 }
13203
13204 void
13205 ToNrEvent(int to)
13206 {
13207   if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13208   if (to >= forwardMostMove) to = forwardMostMove;
13209   if (to <= backwardMostMove) to = backwardMostMove;
13210   if (to < currentMove) {
13211     BackwardInner(to);
13212   } else {
13213     ForwardInner(to);
13214   }
13215 }
13216
13217 void
13218 RevertEvent(Boolean annotate)
13219 {
13220     if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13221         return;
13222     }
13223     if (gameMode != IcsExamining) {
13224         DisplayError(_("You are not examining a game"), 0);
13225         return;
13226     }
13227     if (pausing) {
13228         DisplayError(_("You can't revert while pausing"), 0);
13229         return;
13230     }
13231     SendToICS(ics_prefix);
13232     SendToICS("revert\n");
13233 }
13234
13235 void
13236 RetractMoveEvent()
13237 {
13238     switch (gameMode) {
13239       case MachinePlaysWhite:
13240       case MachinePlaysBlack:
13241         if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13242             DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13243             return;
13244         }
13245         if (forwardMostMove < 2) return;
13246         currentMove = forwardMostMove = forwardMostMove - 2;
13247         whiteTimeRemaining = timeRemaining[0][currentMove];
13248         blackTimeRemaining = timeRemaining[1][currentMove];
13249         DisplayBothClocks();
13250         DisplayMove(currentMove - 1);
13251         ClearHighlights();/*!! could figure this out*/
13252         DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13253         SendToProgram("remove\n", &first);
13254         /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13255         break;
13256
13257       case BeginningOfGame:
13258       default:
13259         break;
13260
13261       case IcsPlayingWhite:
13262       case IcsPlayingBlack:
13263         if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13264             SendToICS(ics_prefix);
13265             SendToICS("takeback 2\n");
13266         } else {
13267             SendToICS(ics_prefix);
13268             SendToICS("takeback 1\n");
13269         }
13270         break;
13271     }
13272 }
13273
13274 void
13275 MoveNowEvent()
13276 {
13277     ChessProgramState *cps;
13278
13279     switch (gameMode) {
13280       case MachinePlaysWhite:
13281         if (!WhiteOnMove(forwardMostMove)) {
13282             DisplayError(_("It is your turn"), 0);
13283             return;
13284         }
13285         cps = &first;
13286         break;
13287       case MachinePlaysBlack:
13288         if (WhiteOnMove(forwardMostMove)) {
13289             DisplayError(_("It is your turn"), 0);
13290             return;
13291         }
13292         cps = &first;
13293         break;
13294       case TwoMachinesPlay:
13295         if (WhiteOnMove(forwardMostMove) ==
13296             (first.twoMachinesColor[0] == 'w')) {
13297             cps = &first;
13298         } else {
13299             cps = &second;
13300         }
13301         break;
13302       case BeginningOfGame:
13303       default:
13304         return;
13305     }
13306     SendToProgram("?\n", cps);
13307 }
13308
13309 void
13310 TruncateGameEvent()
13311 {
13312     EditGameEvent();
13313     if (gameMode != EditGame) return;
13314     TruncateGame();
13315 }
13316
13317 void
13318 TruncateGame()
13319 {
13320     CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13321     if (forwardMostMove > currentMove) {
13322         if (gameInfo.resultDetails != NULL) {
13323             free(gameInfo.resultDetails);
13324             gameInfo.resultDetails = NULL;
13325             gameInfo.result = GameUnfinished;
13326         }
13327         forwardMostMove = currentMove;
13328         HistorySet(parseList, backwardMostMove, forwardMostMove,
13329                    currentMove-1);
13330     }
13331 }
13332
13333 void
13334 HintEvent()
13335 {
13336     if (appData.noChessProgram) return;
13337     switch (gameMode) {
13338       case MachinePlaysWhite:
13339         if (WhiteOnMove(forwardMostMove)) {
13340             DisplayError(_("Wait until your turn"), 0);
13341             return;
13342         }
13343         break;
13344       case BeginningOfGame:
13345       case MachinePlaysBlack:
13346         if (!WhiteOnMove(forwardMostMove)) {
13347             DisplayError(_("Wait until your turn"), 0);
13348             return;
13349         }
13350         break;
13351       default:
13352         DisplayError(_("No hint available"), 0);
13353         return;
13354     }
13355     SendToProgram("hint\n", &first);
13356     hintRequested = TRUE;
13357 }
13358
13359 void
13360 BookEvent()
13361 {
13362     if (appData.noChessProgram) return;
13363     switch (gameMode) {
13364       case MachinePlaysWhite:
13365         if (WhiteOnMove(forwardMostMove)) {
13366             DisplayError(_("Wait until your turn"), 0);
13367             return;
13368         }
13369         break;
13370       case BeginningOfGame:
13371       case MachinePlaysBlack:
13372         if (!WhiteOnMove(forwardMostMove)) {
13373             DisplayError(_("Wait until your turn"), 0);
13374             return;
13375         }
13376         break;
13377       case EditPosition:
13378         EditPositionDone(TRUE);
13379         break;
13380       case TwoMachinesPlay:
13381         return;
13382       default:
13383         break;
13384     }
13385     SendToProgram("bk\n", &first);
13386     bookOutput[0] = NULLCHAR;
13387     bookRequested = TRUE;
13388 }
13389
13390 void
13391 AboutGameEvent()
13392 {
13393     char *tags = PGNTags(&gameInfo);
13394     TagsPopUp(tags, CmailMsg());
13395     free(tags);
13396 }
13397
13398 /* end button procedures */
13399
13400 void
13401 PrintPosition(fp, move)
13402      FILE *fp;
13403      int move;
13404 {
13405     int i, j;
13406
13407     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13408         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13409             char c = PieceToChar(boards[move][i][j]);
13410             fputc(c == 'x' ? '.' : c, fp);
13411             fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13412         }
13413     }
13414     if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13415       fprintf(fp, "white to play\n");
13416     else
13417       fprintf(fp, "black to play\n");
13418 }
13419
13420 void
13421 PrintOpponents(fp)
13422      FILE *fp;
13423 {
13424     if (gameInfo.white != NULL) {
13425         fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13426     } else {
13427         fprintf(fp, "\n");
13428     }
13429 }
13430
13431 /* Find last component of program's own name, using some heuristics */
13432 void
13433 TidyProgramName(prog, host, buf)
13434      char *prog, *host, buf[MSG_SIZ];
13435 {
13436     char *p, *q;
13437     int local = (strcmp(host, "localhost") == 0);
13438     while (!local && (p = strchr(prog, ';')) != NULL) {
13439         p++;
13440         while (*p == ' ') p++;
13441         prog = p;
13442     }
13443     if (*prog == '"' || *prog == '\'') {
13444         q = strchr(prog + 1, *prog);
13445     } else {
13446         q = strchr(prog, ' ');
13447     }
13448     if (q == NULL) q = prog + strlen(prog);
13449     p = q;
13450     while (p >= prog && *p != '/' && *p != '\\') p--;
13451     p++;
13452     if(p == prog && *p == '"') p++;
13453     if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13454     memcpy(buf, p, q - p);
13455     buf[q - p] = NULLCHAR;
13456     if (!local) {
13457         strcat(buf, "@");
13458         strcat(buf, host);
13459     }
13460 }
13461
13462 char *
13463 TimeControlTagValue()
13464 {
13465     char buf[MSG_SIZ];
13466     if (!appData.clockMode) {
13467       safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13468     } else if (movesPerSession > 0) {
13469       snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13470     } else if (timeIncrement == 0) {
13471       snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13472     } else {
13473       snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13474     }
13475     return StrSave(buf);
13476 }
13477
13478 void
13479 SetGameInfo()
13480 {
13481     /* This routine is used only for certain modes */
13482     VariantClass v = gameInfo.variant;
13483     ChessMove r = GameUnfinished;
13484     char *p = NULL;
13485
13486     if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13487         r = gameInfo.result;
13488         p = gameInfo.resultDetails;
13489         gameInfo.resultDetails = NULL;
13490     }
13491     ClearGameInfo(&gameInfo);
13492     gameInfo.variant = v;
13493
13494     switch (gameMode) {
13495       case MachinePlaysWhite:
13496         gameInfo.event = StrSave( appData.pgnEventHeader );
13497         gameInfo.site = StrSave(HostName());
13498         gameInfo.date = PGNDate();
13499         gameInfo.round = StrSave("-");
13500         gameInfo.white = StrSave(first.tidy);
13501         gameInfo.black = StrSave(UserName());
13502         gameInfo.timeControl = TimeControlTagValue();
13503         break;
13504
13505       case MachinePlaysBlack:
13506         gameInfo.event = StrSave( appData.pgnEventHeader );
13507         gameInfo.site = StrSave(HostName());
13508         gameInfo.date = PGNDate();
13509         gameInfo.round = StrSave("-");
13510         gameInfo.white = StrSave(UserName());
13511         gameInfo.black = StrSave(first.tidy);
13512         gameInfo.timeControl = TimeControlTagValue();
13513         break;
13514
13515       case TwoMachinesPlay:
13516         gameInfo.event = StrSave( appData.pgnEventHeader );
13517         gameInfo.site = StrSave(HostName());
13518         gameInfo.date = PGNDate();
13519         if (matchGame > 0) {
13520             char buf[MSG_SIZ];
13521             snprintf(buf, MSG_SIZ, "%d", matchGame);
13522             gameInfo.round = StrSave(buf);
13523         } else {
13524             gameInfo.round = StrSave("-");
13525         }
13526         if (first.twoMachinesColor[0] == 'w') {
13527             gameInfo.white = StrSave(first.tidy);
13528             gameInfo.black = StrSave(second.tidy);
13529         } else {
13530             gameInfo.white = StrSave(second.tidy);
13531             gameInfo.black = StrSave(first.tidy);
13532         }
13533         gameInfo.timeControl = TimeControlTagValue();
13534         break;
13535
13536       case EditGame:
13537         gameInfo.event = StrSave("Edited game");
13538         gameInfo.site = StrSave(HostName());
13539         gameInfo.date = PGNDate();
13540         gameInfo.round = StrSave("-");
13541         gameInfo.white = StrSave("-");
13542         gameInfo.black = StrSave("-");
13543         gameInfo.result = r;
13544         gameInfo.resultDetails = p;
13545         break;
13546
13547       case EditPosition:
13548         gameInfo.event = StrSave("Edited position");
13549         gameInfo.site = StrSave(HostName());
13550         gameInfo.date = PGNDate();
13551         gameInfo.round = StrSave("-");
13552         gameInfo.white = StrSave("-");
13553         gameInfo.black = StrSave("-");
13554         break;
13555
13556       case IcsPlayingWhite:
13557       case IcsPlayingBlack:
13558       case IcsObserving:
13559       case IcsExamining:
13560         break;
13561
13562       case PlayFromGameFile:
13563         gameInfo.event = StrSave("Game from non-PGN file");
13564         gameInfo.site = StrSave(HostName());
13565         gameInfo.date = PGNDate();
13566         gameInfo.round = StrSave("-");
13567         gameInfo.white = StrSave("?");
13568         gameInfo.black = StrSave("?");
13569         break;
13570
13571       default:
13572         break;
13573     }
13574 }
13575
13576 void
13577 ReplaceComment(index, text)
13578      int index;
13579      char *text;
13580 {
13581     int len;
13582     char *p;
13583     float score;
13584
13585     if(index && sscanf(text, "%f/%d", &score, &len) == 2 && 
13586        pvInfoList[index-1].depth == len &&
13587        fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13588        (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13589     while (*text == '\n') text++;
13590     len = strlen(text);
13591     while (len > 0 && text[len - 1] == '\n') len--;
13592
13593     if (commentList[index] != NULL)
13594       free(commentList[index]);
13595
13596     if (len == 0) {
13597         commentList[index] = NULL;
13598         return;
13599     }
13600   if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
13601       *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
13602       *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
13603     commentList[index] = (char *) malloc(len + 2);
13604     strncpy(commentList[index], text, len);
13605     commentList[index][len] = '\n';
13606     commentList[index][len + 1] = NULLCHAR;
13607   } else {
13608     // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
13609     char *p;
13610     commentList[index] = (char *) malloc(len + 7);
13611     safeStrCpy(commentList[index], "{\n", 3);
13612     safeStrCpy(commentList[index]+2, text, len+1);
13613     commentList[index][len+2] = NULLCHAR;
13614     while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
13615     strcat(commentList[index], "\n}\n");
13616   }
13617 }
13618
13619 void
13620 CrushCRs(text)
13621      char *text;
13622 {
13623   char *p = text;
13624   char *q = text;
13625   char ch;
13626
13627   do {
13628     ch = *p++;
13629     if (ch == '\r') continue;
13630     *q++ = ch;
13631   } while (ch != '\0');
13632 }
13633
13634 void
13635 AppendComment(index, text, addBraces)
13636      int index;
13637      char *text;
13638      Boolean addBraces; // [HGM] braces: tells if we should add {}
13639 {
13640     int oldlen, len;
13641     char *old;
13642
13643 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
13644     text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
13645
13646     CrushCRs(text);
13647     while (*text == '\n') text++;
13648     len = strlen(text);
13649     while (len > 0 && text[len - 1] == '\n') len--;
13650
13651     if (len == 0) return;
13652
13653     if (commentList[index] != NULL) {
13654         old = commentList[index];
13655         oldlen = strlen(old);
13656         while(commentList[index][oldlen-1] ==  '\n')
13657           commentList[index][--oldlen] = NULLCHAR;
13658         commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
13659         safeStrCpy(commentList[index], old, oldlen + len + 6);
13660         free(old);
13661         // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
13662         if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
13663           if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
13664           while (*text == '\n') { text++; len--; }
13665           commentList[index][--oldlen] = NULLCHAR;
13666       }
13667         if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
13668         else          strcat(commentList[index], "\n");
13669         strcat(commentList[index], text);
13670         if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
13671         else          strcat(commentList[index], "\n");
13672     } else {
13673         commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
13674         if(addBraces)
13675           safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
13676         else commentList[index][0] = NULLCHAR;
13677         strcat(commentList[index], text);
13678         strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
13679         if(addBraces == TRUE) strcat(commentList[index], "}\n");
13680     }
13681 }
13682
13683 static char * FindStr( char * text, char * sub_text )
13684 {
13685     char * result = strstr( text, sub_text );
13686
13687     if( result != NULL ) {
13688         result += strlen( sub_text );
13689     }
13690
13691     return result;
13692 }
13693
13694 /* [AS] Try to extract PV info from PGN comment */
13695 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
13696 char *GetInfoFromComment( int index, char * text )
13697 {
13698     char * sep = text, *p;
13699
13700     if( text != NULL && index > 0 ) {
13701         int score = 0;
13702         int depth = 0;
13703         int time = -1, sec = 0, deci;
13704         char * s_eval = FindStr( text, "[%eval " );
13705         char * s_emt = FindStr( text, "[%emt " );
13706
13707         if( s_eval != NULL || s_emt != NULL ) {
13708             /* New style */
13709             char delim;
13710
13711             if( s_eval != NULL ) {
13712                 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
13713                     return text;
13714                 }
13715
13716                 if( delim != ']' ) {
13717                     return text;
13718                 }
13719             }
13720
13721             if( s_emt != NULL ) {
13722             }
13723                 return text;
13724         }
13725         else {
13726             /* We expect something like: [+|-]nnn.nn/dd */
13727             int score_lo = 0;
13728
13729             if(*text != '{') return text; // [HGM] braces: must be normal comment
13730
13731             sep = strchr( text, '/' );
13732             if( sep == NULL || sep < (text+4) ) {
13733                 return text;
13734             }
13735
13736             p = text;
13737             if(p[1] == '(') { // comment starts with PV
13738                p = strchr(p, ')'); // locate end of PV
13739                if(p == NULL || sep < p+5) return text;
13740                // at this point we have something like "{(.*) +0.23/6 ..."
13741                p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
13742                *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
13743                // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
13744             }
13745             time = -1; sec = -1; deci = -1;
13746             if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
13747                 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
13748                 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
13749                 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3   ) {
13750                 return text;
13751             }
13752
13753             if( score_lo < 0 || score_lo >= 100 ) {
13754                 return text;
13755             }
13756
13757             if(sec >= 0) time = 600*time + 10*sec; else
13758             if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
13759
13760             score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
13761
13762             /* [HGM] PV time: now locate end of PV info */
13763             while( *++sep >= '0' && *sep <= '9'); // strip depth
13764             if(time >= 0)
13765             while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
13766             if(sec >= 0)
13767             while( *++sep >= '0' && *sep <= '9'); // strip seconds
13768             if(deci >= 0)
13769             while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
13770             while(*sep == ' ') sep++;
13771         }
13772
13773         if( depth <= 0 ) {
13774             return text;
13775         }
13776
13777         if( time < 0 ) {
13778             time = -1;
13779         }
13780
13781         pvInfoList[index-1].depth = depth;
13782         pvInfoList[index-1].score = score;
13783         pvInfoList[index-1].time  = 10*time; // centi-sec
13784         if(*sep == '}') *sep = 0; else *--sep = '{';
13785         if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
13786     }
13787     return sep;
13788 }
13789
13790 void
13791 SendToProgram(message, cps)
13792      char *message;
13793      ChessProgramState *cps;
13794 {
13795     int count, outCount, error;
13796     char buf[MSG_SIZ];
13797
13798     if (cps->pr == NULL) return;
13799     Attention(cps);
13800
13801     if (appData.debugMode) {
13802         TimeMark now;
13803         GetTimeMark(&now);
13804         fprintf(debugFP, "%ld >%-6s: %s",
13805                 SubtractTimeMarks(&now, &programStartTime),
13806                 cps->which, message);
13807     }
13808
13809     count = strlen(message);
13810     outCount = OutputToProcess(cps->pr, message, count, &error);
13811     if (outCount < count && !exiting
13812                          && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
13813       if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
13814       snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
13815         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13816             if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13817                 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13818                 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13819             } else {
13820                 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13821             }
13822             gameInfo.resultDetails = StrSave(buf);
13823         }
13824         if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13825     }
13826 }
13827
13828 void
13829 ReceiveFromProgram(isr, closure, message, count, error)
13830      InputSourceRef isr;
13831      VOIDSTAR closure;
13832      char *message;
13833      int count;
13834      int error;
13835 {
13836     char *end_str;
13837     char buf[MSG_SIZ];
13838     ChessProgramState *cps = (ChessProgramState *)closure;
13839
13840     if (isr != cps->isr) return; /* Killed intentionally */
13841     if (count <= 0) {
13842         if (count == 0) {
13843             snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
13844                     _(cps->which), cps->program);
13845         if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
13846                 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
13847                     gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
13848                     snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
13849                 } else {
13850                     gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
13851                 }
13852                 gameInfo.resultDetails = StrSave(buf);
13853             }
13854             RemoveInputSource(cps->isr);
13855             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
13856         } else {
13857             snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
13858                     _(cps->which), cps->program);
13859             RemoveInputSource(cps->isr);
13860
13861             /* [AS] Program is misbehaving badly... kill it */
13862             if( count == -2 ) {
13863                 DestroyChildProcess( cps->pr, 9 );
13864                 cps->pr = NoProc;
13865             }
13866
13867             if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
13868         }
13869         return;
13870     }
13871
13872     if ((end_str = strchr(message, '\r')) != NULL)
13873       *end_str = NULLCHAR;
13874     if ((end_str = strchr(message, '\n')) != NULL)
13875       *end_str = NULLCHAR;
13876
13877     if (appData.debugMode) {
13878         TimeMark now; int print = 1;
13879         char *quote = ""; char c; int i;
13880
13881         if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
13882                 char start = message[0];
13883                 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
13884                 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
13885                    sscanf(message, "move %c", &c)!=1  && sscanf(message, "offer%c", &c)!=1 &&
13886                    sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
13887                    sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
13888                    sscanf(message, "tell%c", &c)!=1   && sscanf(message, "0-1 %c", &c)!=1 &&
13889                    sscanf(message, "1-0 %c", &c)!=1   && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
13890                    sscanf(message, "setboard %c", &c)!=1   && sscanf(message, "setup %c", &c)!=1 &&
13891                    sscanf(message, "hint: %c", &c)!=1 && 
13892                    sscanf(message, "pong %c", &c)!=1   && start != '#') {
13893                     quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
13894                     print = (appData.engineComments >= 2);
13895                 }
13896                 message[0] = start; // restore original message
13897         }
13898         if(print) {
13899                 GetTimeMark(&now);
13900                 fprintf(debugFP, "%ld <%-6s: %s%s\n",
13901                         SubtractTimeMarks(&now, &programStartTime), cps->which,
13902                         quote,
13903                         message);
13904         }
13905     }
13906
13907     /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
13908     if (appData.icsEngineAnalyze) {
13909         if (strstr(message, "whisper") != NULL ||
13910              strstr(message, "kibitz") != NULL ||
13911             strstr(message, "tellics") != NULL) return;
13912     }
13913
13914     HandleMachineMove(message, cps);
13915 }
13916
13917
13918 void
13919 SendTimeControl(cps, mps, tc, inc, sd, st)
13920      ChessProgramState *cps;
13921      int mps, inc, sd, st;
13922      long tc;
13923 {
13924     char buf[MSG_SIZ];
13925     int seconds;
13926
13927     if( timeControl_2 > 0 ) {
13928         if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
13929             tc = timeControl_2;
13930         }
13931     }
13932     tc  /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
13933     inc /= cps->timeOdds;
13934     st  /= cps->timeOdds;
13935
13936     seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
13937
13938     if (st > 0) {
13939       /* Set exact time per move, normally using st command */
13940       if (cps->stKludge) {
13941         /* GNU Chess 4 has no st command; uses level in a nonstandard way */
13942         seconds = st % 60;
13943         if (seconds == 0) {
13944           snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
13945         } else {
13946           snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
13947         }
13948       } else {
13949         snprintf(buf, MSG_SIZ, "st %d\n", st);
13950       }
13951     } else {
13952       /* Set conventional or incremental time control, using level command */
13953       if (seconds == 0) {
13954         /* Note old gnuchess bug -- minutes:seconds used to not work.
13955            Fixed in later versions, but still avoid :seconds
13956            when seconds is 0. */
13957         snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
13958       } else {
13959         snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
13960                  seconds, inc/1000.);
13961       }
13962     }
13963     SendToProgram(buf, cps);
13964
13965     /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
13966     /* Orthogonally, limit search to given depth */
13967     if (sd > 0) {
13968       if (cps->sdKludge) {
13969         snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
13970       } else {
13971         snprintf(buf, MSG_SIZ, "sd %d\n", sd);
13972       }
13973       SendToProgram(buf, cps);
13974     }
13975
13976     if(cps->nps >= 0) { /* [HGM] nps */
13977         if(cps->supportsNPS == FALSE)
13978           cps->nps = -1; // don't use if engine explicitly says not supported!
13979         else {
13980           snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
13981           SendToProgram(buf, cps);
13982         }
13983     }
13984 }
13985
13986 ChessProgramState *WhitePlayer()
13987 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
13988 {
13989     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
13990        gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
13991         return &second;
13992     return &first;
13993 }
13994
13995 void
13996 SendTimeRemaining(cps, machineWhite)
13997      ChessProgramState *cps;
13998      int /*boolean*/ machineWhite;
13999 {
14000     char message[MSG_SIZ];
14001     long time, otime;
14002
14003     /* Note: this routine must be called when the clocks are stopped
14004        or when they have *just* been set or switched; otherwise
14005        it will be off by the time since the current tick started.
14006     */
14007     if (machineWhite) {
14008         time = whiteTimeRemaining / 10;
14009         otime = blackTimeRemaining / 10;
14010     } else {
14011         time = blackTimeRemaining / 10;
14012         otime = whiteTimeRemaining / 10;
14013     }
14014     /* [HGM] translate opponent's time by time-odds factor */
14015     otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14016     if (appData.debugMode) {
14017         fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14018     }
14019
14020     if (time <= 0) time = 1;
14021     if (otime <= 0) otime = 1;
14022
14023     snprintf(message, MSG_SIZ, "time %ld\n", time);
14024     SendToProgram(message, cps);
14025
14026     snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14027     SendToProgram(message, cps);
14028 }
14029
14030 int
14031 BoolFeature(p, name, loc, cps)
14032      char **p;
14033      char *name;
14034      int *loc;
14035      ChessProgramState *cps;
14036 {
14037   char buf[MSG_SIZ];
14038   int len = strlen(name);
14039   int val;
14040
14041   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14042     (*p) += len + 1;
14043     sscanf(*p, "%d", &val);
14044     *loc = (val != 0);
14045     while (**p && **p != ' ')
14046       (*p)++;
14047     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14048     SendToProgram(buf, cps);
14049     return TRUE;
14050   }
14051   return FALSE;
14052 }
14053
14054 int
14055 IntFeature(p, name, loc, cps)
14056      char **p;
14057      char *name;
14058      int *loc;
14059      ChessProgramState *cps;
14060 {
14061   char buf[MSG_SIZ];
14062   int len = strlen(name);
14063   if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14064     (*p) += len + 1;
14065     sscanf(*p, "%d", loc);
14066     while (**p && **p != ' ') (*p)++;
14067     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14068     SendToProgram(buf, cps);
14069     return TRUE;
14070   }
14071   return FALSE;
14072 }
14073
14074 int
14075 StringFeature(p, name, loc, cps)
14076      char **p;
14077      char *name;
14078      char loc[];
14079      ChessProgramState *cps;
14080 {
14081   char buf[MSG_SIZ];
14082   int len = strlen(name);
14083   if (strncmp((*p), name, len) == 0
14084       && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14085     (*p) += len + 2;
14086     sscanf(*p, "%[^\"]", loc);
14087     while (**p && **p != '\"') (*p)++;
14088     if (**p == '\"') (*p)++;
14089     snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14090     SendToProgram(buf, cps);
14091     return TRUE;
14092   }
14093   return FALSE;
14094 }
14095
14096 int
14097 ParseOption(Option *opt, ChessProgramState *cps)
14098 // [HGM] options: process the string that defines an engine option, and determine
14099 // name, type, default value, and allowed value range
14100 {
14101         char *p, *q, buf[MSG_SIZ];
14102         int n, min = (-1)<<31, max = 1<<31, def;
14103
14104         if(p = strstr(opt->name, " -spin ")) {
14105             if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14106             if(max < min) max = min; // enforce consistency
14107             if(def < min) def = min;
14108             if(def > max) def = max;
14109             opt->value = def;
14110             opt->min = min;
14111             opt->max = max;
14112             opt->type = Spin;
14113         } else if((p = strstr(opt->name, " -slider "))) {
14114             // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14115             if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14116             if(max < min) max = min; // enforce consistency
14117             if(def < min) def = min;
14118             if(def > max) def = max;
14119             opt->value = def;
14120             opt->min = min;
14121             opt->max = max;
14122             opt->type = Spin; // Slider;
14123         } else if((p = strstr(opt->name, " -string "))) {
14124             opt->textValue = p+9;
14125             opt->type = TextBox;
14126         } else if((p = strstr(opt->name, " -file "))) {
14127             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14128             opt->textValue = p+7;
14129             opt->type = FileName; // FileName;
14130         } else if((p = strstr(opt->name, " -path "))) {
14131             // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14132             opt->textValue = p+7;
14133             opt->type = PathName; // PathName;
14134         } else if(p = strstr(opt->name, " -check ")) {
14135             if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14136             opt->value = (def != 0);
14137             opt->type = CheckBox;
14138         } else if(p = strstr(opt->name, " -combo ")) {
14139             opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14140             cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14141             if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14142             opt->value = n = 0;
14143             while(q = StrStr(q, " /// ")) {
14144                 n++; *q = 0;    // count choices, and null-terminate each of them
14145                 q += 5;
14146                 if(*q == '*') { // remember default, which is marked with * prefix
14147                     q++;
14148                     opt->value = n;
14149                 }
14150                 cps->comboList[cps->comboCnt++] = q;
14151             }
14152             cps->comboList[cps->comboCnt++] = NULL;
14153             opt->max = n + 1;
14154             opt->type = ComboBox;
14155         } else if(p = strstr(opt->name, " -button")) {
14156             opt->type = Button;
14157         } else if(p = strstr(opt->name, " -save")) {
14158             opt->type = SaveButton;
14159         } else return FALSE;
14160         *p = 0; // terminate option name
14161         // now look if the command-line options define a setting for this engine option.
14162         if(cps->optionSettings && cps->optionSettings[0])
14163             p = strstr(cps->optionSettings, opt->name); else p = NULL;
14164         if(p && (p == cps->optionSettings || p[-1] == ',')) {
14165           snprintf(buf, MSG_SIZ, "option %s", p);
14166                 if(p = strstr(buf, ",")) *p = 0;
14167                 if(q = strchr(buf, '=')) switch(opt->type) {
14168                     case ComboBox:
14169                         for(n=0; n<opt->max; n++)
14170                             if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14171                         break;
14172                     case TextBox:
14173                         safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14174                         break;
14175                     case Spin:
14176                     case CheckBox:
14177                         opt->value = atoi(q+1);
14178                     default:
14179                         break;
14180                 }
14181                 strcat(buf, "\n");
14182                 SendToProgram(buf, cps);
14183         }
14184         return TRUE;
14185 }
14186
14187 void
14188 FeatureDone(cps, val)
14189      ChessProgramState* cps;
14190      int val;
14191 {
14192   DelayedEventCallback cb = GetDelayedEvent();
14193   if ((cb == InitBackEnd3 && cps == &first) ||
14194       (cb == SettingsMenuIfReady && cps == &second) ||
14195       (cb == LoadEngine) ||
14196       (cb == TwoMachinesEventIfReady && cps == &second)) {
14197     CancelDelayedEvent();
14198     ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14199   }
14200   cps->initDone = val;
14201 }
14202
14203 /* Parse feature command from engine */
14204 void
14205 ParseFeatures(args, cps)
14206      char* args;
14207      ChessProgramState *cps;
14208 {
14209   char *p = args;
14210   char *q;
14211   int val;
14212   char buf[MSG_SIZ];
14213
14214   for (;;) {
14215     while (*p == ' ') p++;
14216     if (*p == NULLCHAR) return;
14217
14218     if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14219     if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14220     if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14221     if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14222     if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14223     if (BoolFeature(&p, "reuse", &val, cps)) {
14224       /* Engine can disable reuse, but can't enable it if user said no */
14225       if (!val) cps->reuse = FALSE;
14226       continue;
14227     }
14228     if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14229     if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14230       if (gameMode == TwoMachinesPlay) {
14231         DisplayTwoMachinesTitle();
14232       } else {
14233         DisplayTitle("");
14234       }
14235       continue;
14236     }
14237     if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14238     if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14239     if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14240     if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14241     if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14242     if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14243     if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14244     if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14245     if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14246     if (IntFeature(&p, "done", &val, cps)) {
14247       FeatureDone(cps, val);
14248       continue;
14249     }
14250     /* Added by Tord: */
14251     if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14252     if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14253     /* End of additions by Tord */
14254
14255     /* [HGM] added features: */
14256     if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14257     if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14258     if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14259     if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14260     if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14261     if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14262     if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14263         if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14264           snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14265             SendToProgram(buf, cps);
14266             continue;
14267         }
14268         if(cps->nrOptions >= MAX_OPTIONS) {
14269             cps->nrOptions--;
14270             snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14271             DisplayError(buf, 0);
14272         }
14273         continue;
14274     }
14275     /* End of additions by HGM */
14276
14277     /* unknown feature: complain and skip */
14278     q = p;
14279     while (*q && *q != '=') q++;
14280     snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14281     SendToProgram(buf, cps);
14282     p = q;
14283     if (*p == '=') {
14284       p++;
14285       if (*p == '\"') {
14286         p++;
14287         while (*p && *p != '\"') p++;
14288         if (*p == '\"') p++;
14289       } else {
14290         while (*p && *p != ' ') p++;
14291       }
14292     }
14293   }
14294
14295 }
14296
14297 void
14298 PeriodicUpdatesEvent(newState)
14299      int newState;
14300 {
14301     if (newState == appData.periodicUpdates)
14302       return;
14303
14304     appData.periodicUpdates=newState;
14305
14306     /* Display type changes, so update it now */
14307 //    DisplayAnalysis();
14308
14309     /* Get the ball rolling again... */
14310     if (newState) {
14311         AnalysisPeriodicEvent(1);
14312         StartAnalysisClock();
14313     }
14314 }
14315
14316 void
14317 PonderNextMoveEvent(newState)
14318      int newState;
14319 {
14320     if (newState == appData.ponderNextMove) return;
14321     if (gameMode == EditPosition) EditPositionDone(TRUE);
14322     if (newState) {
14323         SendToProgram("hard\n", &first);
14324         if (gameMode == TwoMachinesPlay) {
14325             SendToProgram("hard\n", &second);
14326         }
14327     } else {
14328         SendToProgram("easy\n", &first);
14329         thinkOutput[0] = NULLCHAR;
14330         if (gameMode == TwoMachinesPlay) {
14331             SendToProgram("easy\n", &second);
14332         }
14333     }
14334     appData.ponderNextMove = newState;
14335 }
14336
14337 void
14338 NewSettingEvent(option, feature, command, value)
14339      char *command;
14340      int option, value, *feature;
14341 {
14342     char buf[MSG_SIZ];
14343
14344     if (gameMode == EditPosition) EditPositionDone(TRUE);
14345     snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14346     if(feature == NULL || *feature) SendToProgram(buf, &first);
14347     if (gameMode == TwoMachinesPlay) {
14348         if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14349     }
14350 }
14351
14352 void
14353 ShowThinkingEvent()
14354 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14355 {
14356     static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14357     int newState = appData.showThinking
14358         // [HGM] thinking: other features now need thinking output as well
14359         || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14360
14361     if (oldState == newState) return;
14362     oldState = newState;
14363     if (gameMode == EditPosition) EditPositionDone(TRUE);
14364     if (oldState) {
14365         SendToProgram("post\n", &first);
14366         if (gameMode == TwoMachinesPlay) {
14367             SendToProgram("post\n", &second);
14368         }
14369     } else {
14370         SendToProgram("nopost\n", &first);
14371         thinkOutput[0] = NULLCHAR;
14372         if (gameMode == TwoMachinesPlay) {
14373             SendToProgram("nopost\n", &second);
14374         }
14375     }
14376 //    appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14377 }
14378
14379 void
14380 AskQuestionEvent(title, question, replyPrefix, which)
14381      char *title; char *question; char *replyPrefix; char *which;
14382 {
14383   ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14384   if (pr == NoProc) return;
14385   AskQuestion(title, question, replyPrefix, pr);
14386 }
14387
14388 void
14389 TypeInEvent(char firstChar)
14390 {
14391     if ((gameMode == BeginningOfGame && !appData.icsActive) || \r
14392         gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||\r
14393         gameMode == AnalyzeMode || gameMode == EditGame || \r
14394         gameMode == EditPosition || gameMode == IcsExamining ||\r
14395         gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||\r
14396         isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes\r
14397                 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||\r
14398                   gameMode == IcsObserving || gameMode == TwoMachinesPlay    ) ||\r
14399         gameMode == Training) PopUpMoveDialog(firstChar);
14400 }
14401
14402 void
14403 TypeInDoneEvent(char *move)
14404 {
14405         Board board;
14406         int n, fromX, fromY, toX, toY;
14407         char promoChar;
14408         ChessMove moveType;\r
14409
14410         // [HGM] FENedit\r
14411         if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {\r
14412                 EditPositionPasteFEN(move);\r
14413                 return;\r
14414         }\r
14415         // [HGM] movenum: allow move number to be typed in any mode\r
14416         if(sscanf(move, "%d", &n) == 1 && n != 0 ) {\r
14417           ToNrEvent(2*n-1);\r
14418           return;\r
14419         }\r
14420
14421       if (gameMode != EditGame && currentMove != forwardMostMove && \r
14422         gameMode != Training) {\r
14423         DisplayMoveError(_("Displayed move is not current"));\r
14424       } else {\r
14425         int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14426           &moveType, &fromX, &fromY, &toX, &toY, &promoChar);\r
14427         if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized\r
14428         if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove, \r
14429           &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {\r
14430           UserMoveEvent(fromX, fromY, toX, toY, promoChar);     \r
14431         } else {\r
14432           DisplayMoveError(_("Could not parse move"));\r
14433         }
14434       }\r
14435 }\r
14436
14437 void
14438 DisplayMove(moveNumber)
14439      int moveNumber;
14440 {
14441     char message[MSG_SIZ];
14442     char res[MSG_SIZ];
14443     char cpThinkOutput[MSG_SIZ];
14444
14445     if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14446
14447     if (moveNumber == forwardMostMove - 1 ||
14448         gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14449
14450         safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14451
14452         if (strchr(cpThinkOutput, '\n')) {
14453             *strchr(cpThinkOutput, '\n') = NULLCHAR;
14454         }
14455     } else {
14456         *cpThinkOutput = NULLCHAR;
14457     }
14458
14459     /* [AS] Hide thinking from human user */
14460     if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14461         *cpThinkOutput = NULLCHAR;
14462         if( thinkOutput[0] != NULLCHAR ) {
14463             int i;
14464
14465             for( i=0; i<=hiddenThinkOutputState; i++ ) {
14466                 cpThinkOutput[i] = '.';
14467             }
14468             cpThinkOutput[i] = NULLCHAR;
14469             hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14470         }
14471     }
14472
14473     if (moveNumber == forwardMostMove - 1 &&
14474         gameInfo.resultDetails != NULL) {
14475         if (gameInfo.resultDetails[0] == NULLCHAR) {
14476           snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14477         } else {
14478           snprintf(res, MSG_SIZ, " {%s} %s",
14479                     T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14480         }
14481     } else {
14482         res[0] = NULLCHAR;
14483     }
14484
14485     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14486         DisplayMessage(res, cpThinkOutput);
14487     } else {
14488       snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14489                 WhiteOnMove(moveNumber) ? " " : ".. ",
14490                 parseList[moveNumber], res);
14491         DisplayMessage(message, cpThinkOutput);
14492     }
14493 }
14494
14495 void
14496 DisplayComment(moveNumber, text)
14497      int moveNumber;
14498      char *text;
14499 {
14500     char title[MSG_SIZ];
14501     char buf[8000]; // comment can be long!
14502     int score, depth;
14503
14504     if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14505       safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14506     } else {
14507       snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14508               WhiteOnMove(moveNumber) ? " " : ".. ",
14509               parseList[moveNumber]);
14510     }
14511     // [HGM] PV info: display PV info together with (or as) comment
14512     if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14513       if(text == NULL) text = "";
14514       score = pvInfoList[moveNumber].score;
14515       snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14516               depth, (pvInfoList[moveNumber].time+50)/100, text);
14517       text = buf;
14518     }
14519     if (text != NULL && (appData.autoDisplayComment || commentUp))
14520         CommentPopUp(title, text);
14521 }
14522
14523 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14524  * might be busy thinking or pondering.  It can be omitted if your
14525  * gnuchess is configured to stop thinking immediately on any user
14526  * input.  However, that gnuchess feature depends on the FIONREAD
14527  * ioctl, which does not work properly on some flavors of Unix.
14528  */
14529 void
14530 Attention(cps)
14531      ChessProgramState *cps;
14532 {
14533 #if ATTENTION
14534     if (!cps->useSigint) return;
14535     if (appData.noChessProgram || (cps->pr == NoProc)) return;
14536     switch (gameMode) {
14537       case MachinePlaysWhite:
14538       case MachinePlaysBlack:
14539       case TwoMachinesPlay:
14540       case IcsPlayingWhite:
14541       case IcsPlayingBlack:
14542       case AnalyzeMode:
14543       case AnalyzeFile:
14544         /* Skip if we know it isn't thinking */
14545         if (!cps->maybeThinking) return;
14546         if (appData.debugMode)
14547           fprintf(debugFP, "Interrupting %s\n", cps->which);
14548         InterruptChildProcess(cps->pr);
14549         cps->maybeThinking = FALSE;
14550         break;
14551       default:
14552         break;
14553     }
14554 #endif /*ATTENTION*/
14555 }
14556
14557 int
14558 CheckFlags()
14559 {
14560     if (whiteTimeRemaining <= 0) {
14561         if (!whiteFlag) {
14562             whiteFlag = TRUE;
14563             if (appData.icsActive) {
14564                 if (appData.autoCallFlag &&
14565                     gameMode == IcsPlayingBlack && !blackFlag) {
14566                   SendToICS(ics_prefix);
14567                   SendToICS("flag\n");
14568                 }
14569             } else {
14570                 if (blackFlag) {
14571                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14572                 } else {
14573                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14574                     if (appData.autoCallFlag) {
14575                         GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14576                         return TRUE;
14577                     }
14578                 }
14579             }
14580         }
14581     }
14582     if (blackTimeRemaining <= 0) {
14583         if (!blackFlag) {
14584             blackFlag = TRUE;
14585             if (appData.icsActive) {
14586                 if (appData.autoCallFlag &&
14587                     gameMode == IcsPlayingWhite && !whiteFlag) {
14588                   SendToICS(ics_prefix);
14589                   SendToICS("flag\n");
14590                 }
14591             } else {
14592                 if (whiteFlag) {
14593                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14594                 } else {
14595                     if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
14596                     if (appData.autoCallFlag) {
14597                         GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
14598                         return TRUE;
14599                     }
14600                 }
14601             }
14602         }
14603     }
14604     return FALSE;
14605 }
14606
14607 void
14608 CheckTimeControl()
14609 {
14610     if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
14611         gameMode == PlayFromGameFile || forwardMostMove == 0) return;
14612
14613     /*
14614      * add time to clocks when time control is achieved ([HGM] now also used for increment)
14615      */
14616     if ( !WhiteOnMove(forwardMostMove) ) {
14617         /* White made time control */
14618         lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
14619         whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
14620         /* [HGM] time odds: correct new time quota for time odds! */
14621                                             / WhitePlayer()->timeOdds;
14622         lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
14623     } else {
14624         lastBlack -= blackTimeRemaining;
14625         /* Black made time control */
14626         blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
14627                                             / WhitePlayer()->other->timeOdds;
14628         lastWhite = whiteTimeRemaining;
14629     }
14630 }
14631
14632 void
14633 DisplayBothClocks()
14634 {
14635     int wom = gameMode == EditPosition ?
14636       !blackPlaysFirst : WhiteOnMove(currentMove);
14637     DisplayWhiteClock(whiteTimeRemaining, wom);
14638     DisplayBlackClock(blackTimeRemaining, !wom);
14639 }
14640
14641
14642 /* Timekeeping seems to be a portability nightmare.  I think everyone
14643    has ftime(), but I'm really not sure, so I'm including some ifdefs
14644    to use other calls if you don't.  Clocks will be less accurate if
14645    you have neither ftime nor gettimeofday.
14646 */
14647
14648 /* VS 2008 requires the #include outside of the function */
14649 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
14650 #include <sys/timeb.h>
14651 #endif
14652
14653 /* Get the current time as a TimeMark */
14654 void
14655 GetTimeMark(tm)
14656      TimeMark *tm;
14657 {
14658 #if HAVE_GETTIMEOFDAY
14659
14660     struct timeval timeVal;
14661     struct timezone timeZone;
14662
14663     gettimeofday(&timeVal, &timeZone);
14664     tm->sec = (long) timeVal.tv_sec;
14665     tm->ms = (int) (timeVal.tv_usec / 1000L);
14666
14667 #else /*!HAVE_GETTIMEOFDAY*/
14668 #if HAVE_FTIME
14669
14670 // include <sys/timeb.h> / moved to just above start of function
14671     struct timeb timeB;
14672
14673     ftime(&timeB);
14674     tm->sec = (long) timeB.time;
14675     tm->ms = (int) timeB.millitm;
14676
14677 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
14678     tm->sec = (long) time(NULL);
14679     tm->ms = 0;
14680 #endif
14681 #endif
14682 }
14683
14684 /* Return the difference in milliseconds between two
14685    time marks.  We assume the difference will fit in a long!
14686 */
14687 long
14688 SubtractTimeMarks(tm2, tm1)
14689      TimeMark *tm2, *tm1;
14690 {
14691     return 1000L*(tm2->sec - tm1->sec) +
14692            (long) (tm2->ms - tm1->ms);
14693 }
14694
14695
14696 /*
14697  * Code to manage the game clocks.
14698  *
14699  * In tournament play, black starts the clock and then white makes a move.
14700  * We give the human user a slight advantage if he is playing white---the
14701  * clocks don't run until he makes his first move, so it takes zero time.
14702  * Also, we don't account for network lag, so we could get out of sync
14703  * with GNU Chess's clock -- but then, referees are always right.
14704  */
14705
14706 static TimeMark tickStartTM;
14707 static long intendedTickLength;
14708
14709 long
14710 NextTickLength(timeRemaining)
14711      long timeRemaining;
14712 {
14713     long nominalTickLength, nextTickLength;
14714
14715     if (timeRemaining > 0L && timeRemaining <= 10000L)
14716       nominalTickLength = 100L;
14717     else
14718       nominalTickLength = 1000L;
14719     nextTickLength = timeRemaining % nominalTickLength;
14720     if (nextTickLength <= 0) nextTickLength += nominalTickLength;
14721
14722     return nextTickLength;
14723 }
14724
14725 /* Adjust clock one minute up or down */
14726 void
14727 AdjustClock(Boolean which, int dir)
14728 {
14729     if(which) blackTimeRemaining += 60000*dir;
14730     else      whiteTimeRemaining += 60000*dir;
14731     DisplayBothClocks();
14732 }
14733
14734 /* Stop clocks and reset to a fresh time control */
14735 void
14736 ResetClocks()
14737 {
14738     (void) StopClockTimer();
14739     if (appData.icsActive) {
14740         whiteTimeRemaining = blackTimeRemaining = 0;
14741     } else if (searchTime) {
14742         whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14743         blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14744     } else { /* [HGM] correct new time quote for time odds */
14745         whiteTC = blackTC = fullTimeControlString;
14746         whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
14747         blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
14748     }
14749     if (whiteFlag || blackFlag) {
14750         DisplayTitle("");
14751         whiteFlag = blackFlag = FALSE;
14752     }
14753     lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
14754     DisplayBothClocks();
14755 }
14756
14757 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
14758
14759 /* Decrement running clock by amount of time that has passed */
14760 void
14761 DecrementClocks()
14762 {
14763     long timeRemaining;
14764     long lastTickLength, fudge;
14765     TimeMark now;
14766
14767     if (!appData.clockMode) return;
14768     if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
14769
14770     GetTimeMark(&now);
14771
14772     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14773
14774     /* Fudge if we woke up a little too soon */
14775     fudge = intendedTickLength - lastTickLength;
14776     if (fudge < 0 || fudge > FUDGE) fudge = 0;
14777
14778     if (WhiteOnMove(forwardMostMove)) {
14779         if(whiteNPS >= 0) lastTickLength = 0;
14780         timeRemaining = whiteTimeRemaining -= lastTickLength;
14781         if(timeRemaining < 0 && !appData.icsActive) {
14782             GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
14783             if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
14784                 whiteStartMove = forwardMostMove; whiteTC = nextSession;
14785                 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
14786             }
14787         }
14788         DisplayWhiteClock(whiteTimeRemaining - fudge,
14789                           WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14790     } else {
14791         if(blackNPS >= 0) lastTickLength = 0;
14792         timeRemaining = blackTimeRemaining -= lastTickLength;
14793         if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
14794             GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
14795             if(suddenDeath) {
14796                 blackStartMove = forwardMostMove;
14797                 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
14798             }
14799         }
14800         DisplayBlackClock(blackTimeRemaining - fudge,
14801                           !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
14802     }
14803     if (CheckFlags()) return;
14804
14805     tickStartTM = now;
14806     intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
14807     StartClockTimer(intendedTickLength);
14808
14809     /* if the time remaining has fallen below the alarm threshold, sound the
14810      * alarm. if the alarm has sounded and (due to a takeback or time control
14811      * with increment) the time remaining has increased to a level above the
14812      * threshold, reset the alarm so it can sound again.
14813      */
14814
14815     if (appData.icsActive && appData.icsAlarm) {
14816
14817         /* make sure we are dealing with the user's clock */
14818         if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
14819                ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
14820            )) return;
14821
14822         if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
14823             alarmSounded = FALSE;
14824         } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
14825             PlayAlarmSound();
14826             alarmSounded = TRUE;
14827         }
14828     }
14829 }
14830
14831
14832 /* A player has just moved, so stop the previously running
14833    clock and (if in clock mode) start the other one.
14834    We redisplay both clocks in case we're in ICS mode, because
14835    ICS gives us an update to both clocks after every move.
14836    Note that this routine is called *after* forwardMostMove
14837    is updated, so the last fractional tick must be subtracted
14838    from the color that is *not* on move now.
14839 */
14840 void
14841 SwitchClocks(int newMoveNr)
14842 {
14843     long lastTickLength;
14844     TimeMark now;
14845     int flagged = FALSE;
14846
14847     GetTimeMark(&now);
14848
14849     if (StopClockTimer() && appData.clockMode) {
14850         lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14851         if (!WhiteOnMove(forwardMostMove)) {
14852             if(blackNPS >= 0) lastTickLength = 0;
14853             blackTimeRemaining -= lastTickLength;
14854            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14855 //         if(pvInfoList[forwardMostMove].time == -1)
14856                  pvInfoList[forwardMostMove].time =               // use GUI time
14857                       (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
14858         } else {
14859            if(whiteNPS >= 0) lastTickLength = 0;
14860            whiteTimeRemaining -= lastTickLength;
14861            /* [HGM] PGNtime: save time for PGN file if engine did not give it */
14862 //         if(pvInfoList[forwardMostMove].time == -1)
14863                  pvInfoList[forwardMostMove].time =
14864                       (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
14865         }
14866         flagged = CheckFlags();
14867     }
14868     forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
14869     CheckTimeControl();
14870
14871     if (flagged || !appData.clockMode) return;
14872
14873     switch (gameMode) {
14874       case MachinePlaysBlack:
14875       case MachinePlaysWhite:
14876       case BeginningOfGame:
14877         if (pausing) return;
14878         break;
14879
14880       case EditGame:
14881       case PlayFromGameFile:
14882       case IcsExamining:
14883         return;
14884
14885       default:
14886         break;
14887     }
14888
14889     if (searchTime) { // [HGM] st: set clock of player that has to move to max time
14890         if(WhiteOnMove(forwardMostMove))
14891              whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
14892         else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
14893     }
14894
14895     tickStartTM = now;
14896     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14897       whiteTimeRemaining : blackTimeRemaining);
14898     StartClockTimer(intendedTickLength);
14899 }
14900
14901
14902 /* Stop both clocks */
14903 void
14904 StopClocks()
14905 {
14906     long lastTickLength;
14907     TimeMark now;
14908
14909     if (!StopClockTimer()) return;
14910     if (!appData.clockMode) return;
14911
14912     GetTimeMark(&now);
14913
14914     lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
14915     if (WhiteOnMove(forwardMostMove)) {
14916         if(whiteNPS >= 0) lastTickLength = 0;
14917         whiteTimeRemaining -= lastTickLength;
14918         DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
14919     } else {
14920         if(blackNPS >= 0) lastTickLength = 0;
14921         blackTimeRemaining -= lastTickLength;
14922         DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
14923     }
14924     CheckFlags();
14925 }
14926
14927 /* Start clock of player on move.  Time may have been reset, so
14928    if clock is already running, stop and restart it. */
14929 void
14930 StartClocks()
14931 {
14932     (void) StopClockTimer(); /* in case it was running already */
14933     DisplayBothClocks();
14934     if (CheckFlags()) return;
14935
14936     if (!appData.clockMode) return;
14937     if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
14938
14939     GetTimeMark(&tickStartTM);
14940     intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
14941       whiteTimeRemaining : blackTimeRemaining);
14942
14943    /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
14944     whiteNPS = blackNPS = -1;
14945     if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
14946        || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
14947         whiteNPS = first.nps;
14948     if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
14949        || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
14950         blackNPS = first.nps;
14951     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
14952         whiteNPS = second.nps;
14953     if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
14954         blackNPS = second.nps;
14955     if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
14956
14957     StartClockTimer(intendedTickLength);
14958 }
14959
14960 char *
14961 TimeString(ms)
14962      long ms;
14963 {
14964     long second, minute, hour, day;
14965     char *sign = "";
14966     static char buf[32];
14967
14968     if (ms > 0 && ms <= 9900) {
14969       /* convert milliseconds to tenths, rounding up */
14970       double tenths = floor( ((double)(ms + 99L)) / 100.00 );
14971
14972       snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
14973       return buf;
14974     }
14975
14976     /* convert milliseconds to seconds, rounding up */
14977     /* use floating point to avoid strangeness of integer division
14978        with negative dividends on many machines */
14979     second = (long) floor(((double) (ms + 999L)) / 1000.0);
14980
14981     if (second < 0) {
14982         sign = "-";
14983         second = -second;
14984     }
14985
14986     day = second / (60 * 60 * 24);
14987     second = second % (60 * 60 * 24);
14988     hour = second / (60 * 60);
14989     second = second % (60 * 60);
14990     minute = second / 60;
14991     second = second % 60;
14992
14993     if (day > 0)
14994       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
14995               sign, day, hour, minute, second);
14996     else if (hour > 0)
14997       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
14998     else
14999       snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15000
15001     return buf;
15002 }
15003
15004
15005 /*
15006  * This is necessary because some C libraries aren't ANSI C compliant yet.
15007  */
15008 char *
15009 StrStr(string, match)
15010      char *string, *match;
15011 {
15012     int i, length;
15013
15014     length = strlen(match);
15015
15016     for (i = strlen(string) - length; i >= 0; i--, string++)
15017       if (!strncmp(match, string, length))
15018         return string;
15019
15020     return NULL;
15021 }
15022
15023 char *
15024 StrCaseStr(string, match)
15025      char *string, *match;
15026 {
15027     int i, j, length;
15028
15029     length = strlen(match);
15030
15031     for (i = strlen(string) - length; i >= 0; i--, string++) {
15032         for (j = 0; j < length; j++) {
15033             if (ToLower(match[j]) != ToLower(string[j]))
15034               break;
15035         }
15036         if (j == length) return string;
15037     }
15038
15039     return NULL;
15040 }
15041
15042 #ifndef _amigados
15043 int
15044 StrCaseCmp(s1, s2)
15045      char *s1, *s2;
15046 {
15047     char c1, c2;
15048
15049     for (;;) {
15050         c1 = ToLower(*s1++);
15051         c2 = ToLower(*s2++);
15052         if (c1 > c2) return 1;
15053         if (c1 < c2) return -1;
15054         if (c1 == NULLCHAR) return 0;
15055     }
15056 }
15057
15058
15059 int
15060 ToLower(c)
15061      int c;
15062 {
15063     return isupper(c) ? tolower(c) : c;
15064 }
15065
15066
15067 int
15068 ToUpper(c)
15069      int c;
15070 {
15071     return islower(c) ? toupper(c) : c;
15072 }
15073 #endif /* !_amigados    */
15074
15075 char *
15076 StrSave(s)
15077      char *s;
15078 {
15079   char *ret;
15080
15081   if ((ret = (char *) malloc(strlen(s) + 1)))
15082     {
15083       safeStrCpy(ret, s, strlen(s)+1);
15084     }
15085   return ret;
15086 }
15087
15088 char *
15089 StrSavePtr(s, savePtr)
15090      char *s, **savePtr;
15091 {
15092     if (*savePtr) {
15093         free(*savePtr);
15094     }
15095     if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15096       safeStrCpy(*savePtr, s, strlen(s)+1);
15097     }
15098     return(*savePtr);
15099 }
15100
15101 char *
15102 PGNDate()
15103 {
15104     time_t clock;
15105     struct tm *tm;
15106     char buf[MSG_SIZ];
15107
15108     clock = time((time_t *)NULL);
15109     tm = localtime(&clock);
15110     snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15111             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15112     return StrSave(buf);
15113 }
15114
15115
15116 char *
15117 PositionToFEN(move, overrideCastling)
15118      int move;
15119      char *overrideCastling;
15120 {
15121     int i, j, fromX, fromY, toX, toY;
15122     int whiteToPlay;
15123     char buf[128];
15124     char *p, *q;
15125     int emptycount;
15126     ChessSquare piece;
15127
15128     whiteToPlay = (gameMode == EditPosition) ?
15129       !blackPlaysFirst : (move % 2 == 0);
15130     p = buf;
15131
15132     /* Piece placement data */
15133     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15134         emptycount = 0;
15135         for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15136             if (boards[move][i][j] == EmptySquare) {
15137                 emptycount++;
15138             } else { ChessSquare piece = boards[move][i][j];
15139                 if (emptycount > 0) {
15140                     if(emptycount<10) /* [HGM] can be >= 10 */
15141                         *p++ = '0' + emptycount;
15142                     else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15143                     emptycount = 0;
15144                 }
15145                 if(PieceToChar(piece) == '+') {
15146                     /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15147                     *p++ = '+';
15148                     piece = (ChessSquare)(DEMOTED piece);
15149                 }
15150                 *p++ = PieceToChar(piece);
15151                 if(p[-1] == '~') {
15152                     /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15153                     p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15154                     *p++ = '~';
15155                 }
15156             }
15157         }
15158         if (emptycount > 0) {
15159             if(emptycount<10) /* [HGM] can be >= 10 */
15160                 *p++ = '0' + emptycount;
15161             else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15162             emptycount = 0;
15163         }
15164         *p++ = '/';
15165     }
15166     *(p - 1) = ' ';
15167
15168     /* [HGM] print Crazyhouse or Shogi holdings */
15169     if( gameInfo.holdingsWidth ) {
15170         *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15171         q = p;
15172         for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15173             piece = boards[move][i][BOARD_WIDTH-1];
15174             if( piece != EmptySquare )
15175               for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15176                   *p++ = PieceToChar(piece);
15177         }
15178         for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15179             piece = boards[move][BOARD_HEIGHT-i-1][0];
15180             if( piece != EmptySquare )
15181               for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15182                   *p++ = PieceToChar(piece);
15183         }
15184
15185         if( q == p ) *p++ = '-';
15186         *p++ = ']';
15187         *p++ = ' ';
15188     }
15189
15190     /* Active color */
15191     *p++ = whiteToPlay ? 'w' : 'b';
15192     *p++ = ' ';
15193
15194   if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15195     while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15196   } else {
15197   if(nrCastlingRights) {
15198      q = p;
15199      if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15200        /* [HGM] write directly from rights */
15201            if(boards[move][CASTLING][2] != NoRights &&
15202               boards[move][CASTLING][0] != NoRights   )
15203                 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15204            if(boards[move][CASTLING][2] != NoRights &&
15205               boards[move][CASTLING][1] != NoRights   )
15206                 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15207            if(boards[move][CASTLING][5] != NoRights &&
15208               boards[move][CASTLING][3] != NoRights   )
15209                 *p++ = boards[move][CASTLING][3] + AAA;
15210            if(boards[move][CASTLING][5] != NoRights &&
15211               boards[move][CASTLING][4] != NoRights   )
15212                 *p++ = boards[move][CASTLING][4] + AAA;
15213      } else {
15214
15215         /* [HGM] write true castling rights */
15216         if( nrCastlingRights == 6 ) {
15217             if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15218                boards[move][CASTLING][2] != NoRights  ) *p++ = 'K';
15219             if(boards[move][CASTLING][1] == BOARD_LEFT &&
15220                boards[move][CASTLING][2] != NoRights  ) *p++ = 'Q';
15221             if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15222                boards[move][CASTLING][5] != NoRights  ) *p++ = 'k';
15223             if(boards[move][CASTLING][4] == BOARD_LEFT &&
15224                boards[move][CASTLING][5] != NoRights  ) *p++ = 'q';
15225         }
15226      }
15227      if (q == p) *p++ = '-'; /* No castling rights */
15228      *p++ = ' ';
15229   }
15230
15231   if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15232      gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15233     /* En passant target square */
15234     if (move > backwardMostMove) {
15235         fromX = moveList[move - 1][0] - AAA;
15236         fromY = moveList[move - 1][1] - ONE;
15237         toX = moveList[move - 1][2] - AAA;
15238         toY = moveList[move - 1][3] - ONE;
15239         if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15240             toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15241             boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15242             fromX == toX) {
15243             /* 2-square pawn move just happened */
15244             *p++ = toX + AAA;
15245             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15246         } else {
15247             *p++ = '-';
15248         }
15249     } else if(move == backwardMostMove) {
15250         // [HGM] perhaps we should always do it like this, and forget the above?
15251         if((signed char)boards[move][EP_STATUS] >= 0) {
15252             *p++ = boards[move][EP_STATUS] + AAA;
15253             *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15254         } else {
15255             *p++ = '-';
15256         }
15257     } else {
15258         *p++ = '-';
15259     }
15260     *p++ = ' ';
15261   }
15262   }
15263
15264     /* [HGM] find reversible plies */
15265     {   int i = 0, j=move;
15266
15267         if (appData.debugMode) { int k;
15268             fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15269             for(k=backwardMostMove; k<=forwardMostMove; k++)
15270                 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15271
15272         }
15273
15274         while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15275         if( j == backwardMostMove ) i += initialRulePlies;
15276         sprintf(p, "%d ", i);
15277         p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15278     }
15279     /* Fullmove number */
15280     sprintf(p, "%d", (move / 2) + 1);
15281
15282     return StrSave(buf);
15283 }
15284
15285 Boolean
15286 ParseFEN(board, blackPlaysFirst, fen)
15287     Board board;
15288      int *blackPlaysFirst;
15289      char *fen;
15290 {
15291     int i, j;
15292     char *p, c;
15293     int emptycount;
15294     ChessSquare piece;
15295
15296     p = fen;
15297
15298     /* [HGM] by default clear Crazyhouse holdings, if present */
15299     if(gameInfo.holdingsWidth) {
15300        for(i=0; i<BOARD_HEIGHT; i++) {
15301            board[i][0]             = EmptySquare; /* black holdings */
15302            board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15303            board[i][1]             = (ChessSquare) 0; /* black counts */
15304            board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15305        }
15306     }
15307
15308     /* Piece placement data */
15309     for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15310         j = 0;
15311         for (;;) {
15312             if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15313                 if (*p == '/') p++;
15314                 emptycount = gameInfo.boardWidth - j;
15315                 while (emptycount--)
15316                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15317                 break;
15318 #if(BOARD_FILES >= 10)
15319             } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15320                 p++; emptycount=10;
15321                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15322                 while (emptycount--)
15323                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15324 #endif
15325             } else if (isdigit(*p)) {
15326                 emptycount = *p++ - '0';
15327                 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15328                 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15329                 while (emptycount--)
15330                         board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15331             } else if (*p == '+' || isalpha(*p)) {
15332                 if (j >= gameInfo.boardWidth) return FALSE;
15333                 if(*p=='+') {
15334                     piece = CharToPiece(*++p);
15335                     if(piece == EmptySquare) return FALSE; /* unknown piece */
15336                     piece = (ChessSquare) (PROMOTED piece ); p++;
15337                     if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15338                 } else piece = CharToPiece(*p++);
15339
15340                 if(piece==EmptySquare) return FALSE; /* unknown piece */
15341                 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15342                     piece = (ChessSquare) (PROMOTED piece);
15343                     if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15344                     p++;
15345                 }
15346                 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15347             } else {
15348                 return FALSE;
15349             }
15350         }
15351     }
15352     while (*p == '/' || *p == ' ') p++;
15353
15354     /* [HGM] look for Crazyhouse holdings here */
15355     while(*p==' ') p++;
15356     if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15357         if(*p == '[') p++;
15358         if(*p == '-' ) p++; /* empty holdings */ else {
15359             if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15360             /* if we would allow FEN reading to set board size, we would   */
15361             /* have to add holdings and shift the board read so far here   */
15362             while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15363                 p++;
15364                 if((int) piece >= (int) BlackPawn ) {
15365                     i = (int)piece - (int)BlackPawn;
15366                     i = PieceToNumber((ChessSquare)i);
15367                     if( i >= gameInfo.holdingsSize ) return FALSE;
15368                     board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15369                     board[BOARD_HEIGHT-1-i][1]++;       /* black counts   */
15370                 } else {
15371                     i = (int)piece - (int)WhitePawn;
15372                     i = PieceToNumber((ChessSquare)i);
15373                     if( i >= gameInfo.holdingsSize ) return FALSE;
15374                     board[i][BOARD_WIDTH-1] = piece;    /* white holdings */
15375                     board[i][BOARD_WIDTH-2]++;          /* black holdings */
15376                 }
15377             }
15378         }
15379         if(*p == ']') p++;
15380     }
15381
15382     while(*p == ' ') p++;
15383
15384     /* Active color */
15385     c = *p++;
15386     if(appData.colorNickNames) {
15387       if( c == appData.colorNickNames[0] ) c = 'w'; else
15388       if( c == appData.colorNickNames[1] ) c = 'b';
15389     }
15390     switch (c) {
15391       case 'w':
15392         *blackPlaysFirst = FALSE;
15393         break;
15394       case 'b':
15395         *blackPlaysFirst = TRUE;
15396         break;
15397       default:
15398         return FALSE;
15399     }
15400
15401     /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15402     /* return the extra info in global variiables             */
15403
15404     /* set defaults in case FEN is incomplete */
15405     board[EP_STATUS] = EP_UNKNOWN;
15406     for(i=0; i<nrCastlingRights; i++ ) {
15407         board[CASTLING][i] =
15408             gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15409     }   /* assume possible unless obviously impossible */
15410     if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15411     if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15412     if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15413                                   && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15414     if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15415     if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15416     if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15417                                   && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15418     FENrulePlies = 0;
15419
15420     while(*p==' ') p++;
15421     if(nrCastlingRights) {
15422       if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15423           /* castling indicator present, so default becomes no castlings */
15424           for(i=0; i<nrCastlingRights; i++ ) {
15425                  board[CASTLING][i] = NoRights;
15426           }
15427       }
15428       while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15429              (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15430              ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15431              ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth)   ) {
15432         char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15433
15434         for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15435             if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15436             if(board[0             ][i] == WhiteKing) whiteKingFile = i;
15437         }
15438         if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15439             whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15440         if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15441                                      && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15442         if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15443                                      && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15444         switch(c) {
15445           case'K':
15446               for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15447               board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15448               board[CASTLING][2] = whiteKingFile;
15449               break;
15450           case'Q':
15451               for(i=BOARD_LEFT;  i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15452               board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15453               board[CASTLING][2] = whiteKingFile;
15454               break;
15455           case'k':
15456               for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15457               board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15458               board[CASTLING][5] = blackKingFile;
15459               break;
15460           case'q':
15461               for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15462               board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15463               board[CASTLING][5] = blackKingFile;
15464           case '-':
15465               break;
15466           default: /* FRC castlings */
15467               if(c >= 'a') { /* black rights */
15468                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15469                     if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15470                   if(i == BOARD_RGHT) break;
15471                   board[CASTLING][5] = i;
15472                   c -= AAA;
15473                   if(board[BOARD_HEIGHT-1][c] <  BlackPawn ||
15474                      board[BOARD_HEIGHT-1][c] >= BlackKing   ) break;
15475                   if(c > i)
15476                       board[CASTLING][3] = c;
15477                   else
15478                       board[CASTLING][4] = c;
15479               } else { /* white rights */
15480                   for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15481                     if(board[0][i] == WhiteKing) break;
15482                   if(i == BOARD_RGHT) break;
15483                   board[CASTLING][2] = i;
15484                   c -= AAA - 'a' + 'A';
15485                   if(board[0][c] >= WhiteKing) break;
15486                   if(c > i)
15487                       board[CASTLING][0] = c;
15488                   else
15489                       board[CASTLING][1] = c;
15490               }
15491         }
15492       }
15493       for(i=0; i<nrCastlingRights; i++)
15494         if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15495     if (appData.debugMode) {
15496         fprintf(debugFP, "FEN castling rights:");
15497         for(i=0; i<nrCastlingRights; i++)
15498         fprintf(debugFP, " %d", board[CASTLING][i]);
15499         fprintf(debugFP, "\n");
15500     }
15501
15502       while(*p==' ') p++;
15503     }
15504
15505     /* read e.p. field in games that know e.p. capture */
15506     if(gameInfo.variant != VariantShogi    && gameInfo.variant != VariantXiangqi &&
15507        gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15508       if(*p=='-') {
15509         p++; board[EP_STATUS] = EP_NONE;
15510       } else {
15511          char c = *p++ - AAA;
15512
15513          if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15514          if(*p >= '0' && *p <='9') p++;
15515          board[EP_STATUS] = c;
15516       }
15517     }
15518
15519
15520     if(sscanf(p, "%d", &i) == 1) {
15521         FENrulePlies = i; /* 50-move ply counter */
15522         /* (The move number is still ignored)    */
15523     }
15524
15525     return TRUE;
15526 }
15527
15528 void
15529 EditPositionPasteFEN(char *fen)
15530 {
15531   if (fen != NULL) {
15532     Board initial_position;
15533
15534     if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15535       DisplayError(_("Bad FEN position in clipboard"), 0);
15536       return ;
15537     } else {
15538       int savedBlackPlaysFirst = blackPlaysFirst;
15539       EditPositionEvent();
15540       blackPlaysFirst = savedBlackPlaysFirst;
15541       CopyBoard(boards[0], initial_position);
15542       initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15543       EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15544       DisplayBothClocks();
15545       DrawPosition(FALSE, boards[currentMove]);
15546     }
15547   }
15548 }
15549
15550 static char cseq[12] = "\\   ";
15551
15552 Boolean set_cont_sequence(char *new_seq)
15553 {
15554     int len;
15555     Boolean ret;
15556
15557     // handle bad attempts to set the sequence
15558         if (!new_seq)
15559                 return 0; // acceptable error - no debug
15560
15561     len = strlen(new_seq);
15562     ret = (len > 0) && (len < sizeof(cseq));
15563     if (ret)
15564       safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15565     else if (appData.debugMode)
15566       fprintf(debugFP, "Invalid continuation sequence \"%s\"  (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15567     return ret;
15568 }
15569
15570 /*
15571     reformat a source message so words don't cross the width boundary.  internal
15572     newlines are not removed.  returns the wrapped size (no null character unless
15573     included in source message).  If dest is NULL, only calculate the size required
15574     for the dest buffer.  lp argument indicats line position upon entry, and it's
15575     passed back upon exit.
15576 */
15577 int wrap(char *dest, char *src, int count, int width, int *lp)
15578 {
15579     int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15580
15581     cseq_len = strlen(cseq);
15582     old_line = line = *lp;
15583     ansi = len = clen = 0;
15584
15585     for (i=0; i < count; i++)
15586     {
15587         if (src[i] == '\033')
15588             ansi = 1;
15589
15590         // if we hit the width, back up
15591         if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
15592         {
15593             // store i & len in case the word is too long
15594             old_i = i, old_len = len;
15595
15596             // find the end of the last word
15597             while (i && src[i] != ' ' && src[i] != '\n')
15598             {
15599                 i--;
15600                 len--;
15601             }
15602
15603             // word too long?  restore i & len before splitting it
15604             if ((old_i-i+clen) >= width)
15605             {
15606                 i = old_i;
15607                 len = old_len;
15608             }
15609
15610             // extra space?
15611             if (i && src[i-1] == ' ')
15612                 len--;
15613
15614             if (src[i] != ' ' && src[i] != '\n')
15615             {
15616                 i--;
15617                 if (len)
15618                     len--;
15619             }
15620
15621             // now append the newline and continuation sequence
15622             if (dest)
15623                 dest[len] = '\n';
15624             len++;
15625             if (dest)
15626                 strncpy(dest+len, cseq, cseq_len);
15627             len += cseq_len;
15628             line = cseq_len;
15629             clen = cseq_len;
15630             continue;
15631         }
15632
15633         if (dest)
15634             dest[len] = src[i];
15635         len++;
15636         if (!ansi)
15637             line++;
15638         if (src[i] == '\n')
15639             line = 0;
15640         if (src[i] == 'm')
15641             ansi = 0;
15642     }
15643     if (dest && appData.debugMode)
15644     {
15645         fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
15646             count, width, line, len, *lp);
15647         show_bytes(debugFP, src, count);
15648         fprintf(debugFP, "\ndest: ");
15649         show_bytes(debugFP, dest, len);
15650         fprintf(debugFP, "\n");
15651     }
15652     *lp = dest ? line : old_line;
15653
15654     return len;
15655 }
15656
15657 // [HGM] vari: routines for shelving variations
15658
15659 void
15660 PushTail(int firstMove, int lastMove)
15661 {
15662         int i, j, nrMoves = lastMove - firstMove;
15663
15664         if(appData.icsActive) { // only in local mode
15665                 forwardMostMove = currentMove; // mimic old ICS behavior
15666                 return;
15667         }
15668         if(storedGames >= MAX_VARIATIONS-1) return;
15669
15670         // push current tail of game on stack
15671         savedResult[storedGames] = gameInfo.result;
15672         savedDetails[storedGames] = gameInfo.resultDetails;
15673         gameInfo.resultDetails = NULL;
15674         savedFirst[storedGames] = firstMove;
15675         savedLast [storedGames] = lastMove;
15676         savedFramePtr[storedGames] = framePtr;
15677         framePtr -= nrMoves; // reserve space for the boards
15678         for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
15679             CopyBoard(boards[framePtr+i], boards[firstMove+i]);
15680             for(j=0; j<MOVE_LEN; j++)
15681                 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
15682             for(j=0; j<2*MOVE_LEN; j++)
15683                 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
15684             timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
15685             timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
15686             pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
15687             pvInfoList[firstMove+i-1].depth = 0;
15688             commentList[framePtr+i] = commentList[firstMove+i];
15689             commentList[firstMove+i] = NULL;
15690         }
15691
15692         storedGames++;
15693         forwardMostMove = firstMove; // truncate game so we can start variation
15694         if(storedGames == 1) GreyRevert(FALSE);
15695 }
15696
15697 Boolean
15698 PopTail(Boolean annotate)
15699 {
15700         int i, j, nrMoves;
15701         char buf[8000], moveBuf[20];
15702
15703         if(appData.icsActive) return FALSE; // only in local mode
15704         if(!storedGames) return FALSE; // sanity
15705         CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
15706
15707         storedGames--;
15708         ToNrEvent(savedFirst[storedGames]); // sets currentMove
15709         nrMoves = savedLast[storedGames] - currentMove;
15710         if(annotate) {
15711                 int cnt = 10;
15712                 if(!WhiteOnMove(currentMove))
15713                   snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
15714                 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
15715                 for(i=currentMove; i<forwardMostMove; i++) {
15716                         if(WhiteOnMove(i))
15717                           snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
15718                         else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
15719                         strcat(buf, moveBuf);
15720                         if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
15721                         if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
15722                 }
15723                 strcat(buf, ")");
15724         }
15725         for(i=1; i<=nrMoves; i++) { // copy last variation back
15726             CopyBoard(boards[currentMove+i], boards[framePtr+i]);
15727             for(j=0; j<MOVE_LEN; j++)
15728                 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
15729             for(j=0; j<2*MOVE_LEN; j++)
15730                 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
15731             timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
15732             timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
15733             pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
15734             if(commentList[currentMove+i]) free(commentList[currentMove+i]);
15735             commentList[currentMove+i] = commentList[framePtr+i];
15736             commentList[framePtr+i] = NULL;
15737         }
15738         if(annotate) AppendComment(currentMove+1, buf, FALSE);
15739         framePtr = savedFramePtr[storedGames];
15740         gameInfo.result = savedResult[storedGames];
15741         if(gameInfo.resultDetails != NULL) {
15742             free(gameInfo.resultDetails);
15743       }
15744         gameInfo.resultDetails = savedDetails[storedGames];
15745         forwardMostMove = currentMove + nrMoves;
15746         if(storedGames == 0) GreyRevert(TRUE);
15747         return TRUE;
15748 }
15749
15750 void
15751 CleanupTail()
15752 {       // remove all shelved variations
15753         int i;
15754         for(i=0; i<storedGames; i++) {
15755             if(savedDetails[i])
15756                 free(savedDetails[i]);
15757             savedDetails[i] = NULL;
15758         }
15759         for(i=framePtr; i<MAX_MOVES; i++) {
15760                 if(commentList[i]) free(commentList[i]);
15761                 commentList[i] = NULL;
15762         }
15763         framePtr = MAX_MOVES-1;
15764         storedGames = 0;
15765 }
15766
15767 void
15768 LoadVariation(int index, char *text)
15769 {       // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
15770         char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
15771         int level = 0, move;
15772
15773         if(gameMode != EditGame && gameMode != AnalyzeMode) return;
15774         // first find outermost bracketing variation
15775         while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
15776             if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
15777                 if(*p == '{') wait = '}'; else
15778                 if(*p == '[') wait = ']'; else
15779                 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
15780                 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
15781             }
15782             if(*p == wait) wait = NULLCHAR; // closing ]} found
15783             p++;
15784         }
15785         if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
15786         if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
15787         end[1] = NULLCHAR; // clip off comment beyond variation
15788         ToNrEvent(currentMove-1);
15789         PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
15790         // kludge: use ParsePV() to append variation to game
15791         move = currentMove;
15792         ParsePV(start, TRUE);
15793         forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
15794         ClearPremoveHighlights();
15795         CommentPopDown();
15796         ToNrEvent(currentMove+1);
15797 }
15798